Auth.swift 105 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417
  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. import FirebaseAppCheckInterop
  16. import FirebaseAuthInterop
  17. import FirebaseCore
  18. import FirebaseCoreExtension
  19. #if COCOAPODS
  20. @_implementationOnly import GoogleUtilities
  21. #else
  22. @_implementationOnly import GoogleUtilities_AppDelegateSwizzler
  23. @_implementationOnly import GoogleUtilities_Environment
  24. #endif
  25. #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
  26. import UIKit
  27. #endif
  28. // Export the deprecated Objective-C defined globals and typedefs.
  29. #if SWIFT_PACKAGE
  30. @_exported import FirebaseAuthInternal
  31. #endif // SWIFT_PACKAGE
  32. #if os(iOS)
  33. @available(iOS 13.0, *)
  34. extension Auth: UISceneDelegate {
  35. open func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
  36. for urlContext in URLContexts {
  37. let _ = canHandle(urlContext.url)
  38. }
  39. }
  40. }
  41. @available(iOS 13.0, *)
  42. extension Auth: UIApplicationDelegate {
  43. open func application(_ application: UIApplication,
  44. didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  45. setAPNSToken(deviceToken, type: .unknown)
  46. }
  47. open func application(_ application: UIApplication,
  48. didFailToRegisterForRemoteNotificationsWithError error: Error) {
  49. kAuthGlobalWorkQueue.sync {
  50. self.tokenManager.cancel(withError: error)
  51. }
  52. }
  53. open func application(_ application: UIApplication,
  54. didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  55. fetchCompletionHandler completionHandler:
  56. @escaping (UIBackgroundFetchResult) -> Void) {
  57. _ = canHandleNotification(userInfo)
  58. completionHandler(UIBackgroundFetchResult.noData)
  59. }
  60. open func application(_ application: UIApplication,
  61. open url: URL,
  62. options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
  63. return canHandle(url)
  64. }
  65. }
  66. #endif
  67. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  68. extension Auth: AuthInterop {
  69. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  70. ///
  71. /// This method is not for public use. It is for Firebase clients of AuthInterop.
  72. @objc(getTokenForcingRefresh:withCallback:)
  73. public func getToken(forcingRefresh forceRefresh: Bool,
  74. completion callback: @escaping (String?, Error?) -> Void) {
  75. kAuthGlobalWorkQueue.async { [weak self] in
  76. if let strongSelf = self {
  77. // Enable token auto-refresh if not already enabled.
  78. if !strongSelf.autoRefreshTokens {
  79. AuthLog.logInfo(code: "I-AUT000002", message: "Token auto-refresh enabled.")
  80. strongSelf.autoRefreshTokens = true
  81. strongSelf.scheduleAutoTokenRefresh()
  82. #if os(iOS) || os(tvOS) // TODO(ObjC): Is a similar mechanism needed on macOS?
  83. strongSelf.applicationDidBecomeActiveObserver =
  84. NotificationCenter.default.addObserver(
  85. forName: UIApplication.didBecomeActiveNotification,
  86. object: nil, queue: nil
  87. ) { notification in
  88. if let strongSelf = self {
  89. strongSelf.isAppInBackground = false
  90. if !strongSelf.autoRefreshScheduled {
  91. strongSelf.scheduleAutoTokenRefresh()
  92. }
  93. }
  94. }
  95. strongSelf.applicationDidEnterBackgroundObserver =
  96. NotificationCenter.default.addObserver(
  97. forName: UIApplication.didEnterBackgroundNotification,
  98. object: nil, queue: nil
  99. ) { notification in
  100. if let strongSelf = self {
  101. strongSelf.isAppInBackground = true
  102. }
  103. }
  104. #endif
  105. }
  106. }
  107. // Call back with 'nil' if there is no current user.
  108. guard let strongSelf = self, let currentUser = strongSelf._currentUser else {
  109. DispatchQueue.main.async {
  110. callback(nil, nil)
  111. }
  112. return
  113. }
  114. // Call back with current user token.
  115. currentUser
  116. .internalGetToken(forceRefresh: forceRefresh, backend: strongSelf.backend) { token, error in
  117. DispatchQueue.main.async {
  118. callback(token, error)
  119. }
  120. }
  121. }
  122. }
  123. /// Get the current Auth user's UID. Returns nil if there is no user signed in.
  124. ///
  125. /// This method is not for public use. It is for Firebase clients of AuthInterop.
  126. open func getUserID() -> String? {
  127. return _currentUser?.uid
  128. }
  129. }
  130. /// Manages authentication for Firebase apps.
  131. ///
  132. /// This class is thread-safe.
  133. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  134. @objc(FIRAuth) open class Auth: NSObject {
  135. /// Gets the auth object for the default Firebase app.
  136. ///
  137. /// The default Firebase app must have already been configured or an exception will be raised.
  138. @objc open class func auth() -> Auth {
  139. guard let defaultApp = FirebaseApp.app() else {
  140. fatalError("The default FirebaseApp instance must be configured before the default Auth " +
  141. "instance can be initialized. One way to ensure this is to call " +
  142. "`FirebaseApp.configure()` in the App Delegate's " +
  143. "`application(_:didFinishLaunchingWithOptions:)` (or the `@main` struct's " +
  144. "initializer in SwiftUI).")
  145. }
  146. return auth(app: defaultApp)
  147. }
  148. /// Gets the auth object for a `FirebaseApp`.
  149. /// - Parameter app: The app for which to retrieve the associated `Auth` instance.
  150. /// - Returns: The `Auth` instance associated with the given app.
  151. @objc open class func auth(app: FirebaseApp) -> Auth {
  152. return ComponentType<AuthInterop>.instance(for: AuthInterop.self, in: app.container) as! Auth
  153. }
  154. /// Gets the `FirebaseApp` object that this auth object is connected to.
  155. @objc public internal(set) weak var app: FirebaseApp?
  156. /// Synchronously gets the cached current user, or null if there is none.
  157. @objc public var currentUser: User? {
  158. kAuthGlobalWorkQueue.sync {
  159. _currentUser
  160. }
  161. }
  162. private var _currentUser: User?
  163. /// The current user language code.
  164. ///
  165. /// This property can be set to the app's current language by
  166. /// calling `useAppLanguage()`.
  167. ///
  168. /// The string used to set this property must be a language code that follows BCP 47.
  169. @objc open var languageCode: String? {
  170. get {
  171. kAuthGlobalWorkQueue.sync {
  172. requestConfiguration.languageCode
  173. }
  174. }
  175. set(val) {
  176. kAuthGlobalWorkQueue.sync {
  177. requestConfiguration.languageCode = val
  178. }
  179. }
  180. }
  181. /// Contains settings related to the auth object.
  182. @NSCopying @objc open var settings: AuthSettings?
  183. /// The current user access group that the Auth instance is using.
  184. ///
  185. /// Default is `nil`.
  186. @objc public internal(set) var userAccessGroup: String?
  187. /// Contains shareAuthStateAcrossDevices setting related to the auth object.
  188. ///
  189. /// If userAccessGroup is not set, setting shareAuthStateAcrossDevices will
  190. /// have no effect. You should set shareAuthStateAcrossDevices to its desired
  191. /// state and then set the userAccessGroup after.
  192. @objc open var shareAuthStateAcrossDevices: Bool = false
  193. /// The tenant ID of the auth instance. `nil` if none is available.
  194. @objc open var tenantID: String?
  195. /// The custom authentication domain used to handle all sign-in redirects.
  196. /// End-users will see
  197. /// this domain when signing in. This domain must be allowlisted in the Firebase Console.
  198. @objc open var customAuthDomain: String?
  199. /// Sets the `currentUser` on the receiver to the provided user object.
  200. /// - Parameters:
  201. /// - user: The user object to be set as the current user of the calling Auth instance.
  202. /// - completion: Optionally; a block invoked after the user of the calling Auth instance has
  203. /// been updated or an error was encountered.
  204. @objc open func updateCurrentUser(_ user: User?, completion: ((Error?) -> Void)? = nil) {
  205. kAuthGlobalWorkQueue.async {
  206. guard let user else {
  207. let error = AuthErrorUtils.nullUserError(message: nil)
  208. Auth.wrapMainAsync(completion, error)
  209. return
  210. }
  211. let updateUserBlock: (User) -> Void = { user in
  212. do {
  213. try self.updateCurrentUser(user, byForce: true, savingToDisk: true)
  214. Auth.wrapMainAsync(completion, nil)
  215. } catch {
  216. Auth.wrapMainAsync(completion, error)
  217. }
  218. }
  219. if user.requestConfiguration.apiKey != self.requestConfiguration.apiKey {
  220. // If the API keys are different, then we need to confirm that the user belongs to the same
  221. // project before proceeding.
  222. user.requestConfiguration = self.requestConfiguration
  223. user.reload { error in
  224. if let error {
  225. Auth.wrapMainAsync(completion, error)
  226. return
  227. }
  228. updateUserBlock(user)
  229. }
  230. } else {
  231. updateUserBlock(user)
  232. }
  233. }
  234. }
  235. /// Sets the `currentUser` on the receiver to the provided user object.
  236. /// - Parameter user: The user object to be set as the current user of the calling Auth instance.
  237. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  238. open func updateCurrentUser(_ user: User) async throws {
  239. return try await withCheckedThrowingContinuation { continuation in
  240. self.updateCurrentUser(user) { error in
  241. if let error {
  242. continuation.resume(throwing: error)
  243. } else {
  244. continuation.resume()
  245. }
  246. }
  247. }
  248. }
  249. /// [Deprecated] Fetches the list of all sign-in methods previously used for the provided
  250. /// email address. This method returns an empty list when [Email Enumeration
  251. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  252. /// is enabled, irrespective of the number of authentication methods available for the given
  253. /// email.
  254. ///
  255. /// Possible error codes: `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  256. ///
  257. /// - Parameter email: The email address for which to obtain a list of sign-in methods.
  258. /// - Parameter completion: Optionally; a block which is invoked when the list of sign in methods
  259. /// for the specified email address is ready or an error was encountered. Invoked asynchronously
  260. /// on the main thread in the future.
  261. #if !FIREBASE_CI
  262. @available(
  263. *,
  264. deprecated,
  265. message: "`fetchSignInMethods` is deprecated and will be removed in a future release. This method returns an empty list when Email Enumeration Protection is enabled."
  266. )
  267. #endif // !FIREBASE_CI
  268. @objc open func fetchSignInMethods(forEmail email: String,
  269. completion: (([String]?, Error?) -> Void)? = nil) {
  270. kAuthGlobalWorkQueue.async {
  271. let request = CreateAuthURIRequest(identifier: email,
  272. continueURI: "http://www.google.com/",
  273. requestConfiguration: self.requestConfiguration)
  274. Task {
  275. do {
  276. let response = try await self.backend.call(with: request)
  277. Auth.wrapMainAsync(callback: completion, with: .success(response.signinMethods))
  278. } catch {
  279. Auth.wrapMainAsync(callback: completion, with: .failure(error))
  280. }
  281. }
  282. }
  283. }
  284. /// [Deprecated] Fetches the list of all sign-in methods previously used for the provided
  285. /// email address. This method returns an empty list when [Email Enumeration
  286. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  287. /// is enabled, irrespective of the number of authentication methods available for the given
  288. /// email.
  289. ///
  290. /// Possible error codes: `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  291. ///
  292. /// - Parameter email: The email address for which to obtain a list of sign-in methods.
  293. /// - Returns: List of sign-in methods
  294. @available(
  295. *,
  296. deprecated,
  297. message: "`fetchSignInMethods` is deprecated and will be removed in a future release. This method returns an empty list when Email Enumeration Protection is enabled."
  298. )
  299. open func fetchSignInMethods(forEmail email: String) async throws -> [String] {
  300. return try await withCheckedThrowingContinuation { continuation in
  301. self.fetchSignInMethods(forEmail: email) { methods, error in
  302. if let methods {
  303. continuation.resume(returning: methods)
  304. } else {
  305. continuation.resume(throwing: error!)
  306. }
  307. }
  308. }
  309. }
  310. /// Signs in using an email address and password.
  311. ///
  312. /// When [Email Enumeration
  313. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  314. /// is enabled, this method fails with an error in case of an invalid
  315. /// email/password.
  316. ///
  317. /// Possible error codes:
  318. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  319. /// accounts are not enabled. Enable them in the Auth section of the
  320. /// Firebase console.
  321. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  322. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  323. /// sign in with an incorrect password.
  324. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  325. /// - Parameter email: The user's email address.
  326. /// - Parameter password: The user's password.
  327. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  328. /// or is canceled. Invoked asynchronously on the main thread in the future.
  329. @objc open func signIn(withEmail email: String,
  330. password: String,
  331. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  332. kAuthGlobalWorkQueue.async {
  333. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  334. Task {
  335. do {
  336. let authData = try await self.internalSignInAndRetrieveData(
  337. withEmail: email,
  338. password: password
  339. )
  340. decoratedCallback(.success(authData))
  341. } catch {
  342. decoratedCallback(.failure(error))
  343. }
  344. }
  345. }
  346. }
  347. /// Signs in using an email address and password.
  348. ///
  349. /// When [Email Enumeration
  350. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  351. /// is enabled, this method throws in case of an invalid email/password.
  352. ///
  353. /// Possible error codes:
  354. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  355. /// accounts are not enabled. Enable them in the Auth section of the
  356. /// Firebase console.
  357. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  358. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  359. /// sign in with an incorrect password.
  360. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  361. /// - Parameter email: The user's email address.
  362. /// - Parameter password: The user's password.
  363. /// - Returns: The signed in user.
  364. func internalSignInUser(withEmail email: String,
  365. password: String) async throws -> User {
  366. let request = VerifyPasswordRequest(email: email,
  367. password: password,
  368. requestConfiguration: requestConfiguration)
  369. if request.password.count == 0 {
  370. throw AuthErrorUtils.wrongPasswordError(message: nil)
  371. }
  372. #if os(iOS)
  373. let response = try await injectRecaptcha(request: request,
  374. action: AuthRecaptchaAction.signInWithPassword)
  375. #else
  376. let response = try await backend.call(with: request)
  377. #endif
  378. return try await completeSignIn(
  379. withAccessToken: response.idToken,
  380. accessTokenExpirationDate: response.approximateExpirationDate,
  381. refreshToken: response.refreshToken,
  382. anonymous: false
  383. )
  384. }
  385. /// Signs in using an email address and password.
  386. ///
  387. /// Possible error codes:
  388. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  389. /// accounts are not enabled. Enable them in the Auth section of the
  390. /// Firebase console.
  391. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  392. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  393. /// sign in with an incorrect password.
  394. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  395. /// - Parameter email: The user's email address.
  396. /// - Parameter password: The user's password.
  397. /// - Returns: The `AuthDataResult` after a successful signin.
  398. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  399. @discardableResult
  400. open func signIn(withEmail email: String, password: String) async throws -> AuthDataResult {
  401. return try await withCheckedThrowingContinuation { continuation in
  402. self.signIn(withEmail: email, password: password) { authData, error in
  403. if let authData {
  404. continuation.resume(returning: authData)
  405. } else {
  406. continuation.resume(throwing: error!)
  407. }
  408. }
  409. }
  410. }
  411. /// Signs in using an email address and email sign-in link.
  412. ///
  413. /// Possible error codes:
  414. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  415. /// accounts are not enabled. Enable them in the Auth section of the
  416. /// Firebase console.
  417. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  418. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  419. /// sign in with an incorrect password.
  420. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  421. /// - Parameter email: The user's email address.
  422. /// - Parameter link: The email sign-in link.
  423. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  424. /// or is canceled. Invoked asynchronously on the main thread in the future.
  425. @objc open func signIn(withEmail email: String,
  426. link: String,
  427. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  428. kAuthGlobalWorkQueue.async {
  429. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  430. let credential = EmailAuthCredential(withEmail: email, link: link)
  431. Task {
  432. do {
  433. let authData = try await self.internalSignInAndRetrieveData(withCredential: credential,
  434. isReauthentication: false)
  435. decoratedCallback(.success(authData))
  436. } catch {
  437. decoratedCallback(.failure(error))
  438. }
  439. }
  440. }
  441. }
  442. /// Signs in using an email address and email sign-in link.
  443. /// Possible error codes:
  444. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  445. /// accounts are not enabled. Enable them in the Auth section of the
  446. /// Firebase console.
  447. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  448. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  449. /// sign in with an incorrect password.
  450. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  451. /// - Parameter email: The user's email address.
  452. /// - Parameter link: The email sign-in link.
  453. /// - Returns: The `AuthDataResult` after a successful signin.
  454. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  455. open func signIn(withEmail email: String, link: String) async throws -> AuthDataResult {
  456. return try await withCheckedThrowingContinuation { continuation in
  457. self.signIn(withEmail: email, link: link) { result, error in
  458. if let result {
  459. continuation.resume(returning: result)
  460. } else {
  461. continuation.resume(throwing: error!)
  462. }
  463. }
  464. }
  465. }
  466. #if os(iOS)
  467. /// Signs in using the provided auth provider instance.
  468. ///
  469. /// Possible error codes:
  470. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  471. /// accounts are not enabled. Enable them in the Auth section of the
  472. /// Firebase console.
  473. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  474. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  475. /// sign in with an incorrect password.
  476. /// * `AuthErrorCodeWebNetworkRequestFailed` - Indicates that a network request within a
  477. /// SFSafariViewController or WKWebView failed.
  478. /// * `AuthErrorCodeWebInternalError` - Indicates that an internal error occurred within a
  479. /// SFSafariViewController or WKWebView.
  480. /// * `AuthErrorCodeWebSignInUserInteractionFailure` - Indicates a general failure during
  481. /// a web sign-in flow.
  482. /// * `AuthErrorCodeWebContextAlreadyPresented` - Indicates that an attempt was made to
  483. /// present a new web context while one was already being presented.
  484. /// * `AuthErrorCodeWebContextCancelled` - Indicates that the URL presentation was
  485. /// cancelled prematurely by the user.
  486. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  487. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  488. /// existing account, that cannot be authenticated with this sign-in method. Call
  489. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  490. /// the sign-in providers returned. This error will only be thrown if the "One account per
  491. /// email address" setting is enabled in the Firebase console, under Auth settings.
  492. /// - Parameter provider: An instance of an auth provider used to initiate the sign-in flow.
  493. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the AuthUIDelegate
  494. /// protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate
  495. /// will be used.
  496. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  497. /// or is canceled. Invoked asynchronously on the main thread in the future.
  498. @available(tvOS, unavailable)
  499. @available(macOS, unavailable)
  500. @available(watchOS, unavailable)
  501. @objc(signInWithProvider:UIDelegate:completion:)
  502. open func signIn(with provider: FederatedAuthProvider,
  503. uiDelegate: AuthUIDelegate?,
  504. completion: ((AuthDataResult?, Error?) -> Void)?) {
  505. kAuthGlobalWorkQueue.async {
  506. Task {
  507. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  508. do {
  509. let credential = try await provider.credential(with: uiDelegate)
  510. let authData = try await self.internalSignInAndRetrieveData(
  511. withCredential: credential,
  512. isReauthentication: false
  513. )
  514. decoratedCallback(.success(authData))
  515. } catch {
  516. decoratedCallback(.failure(error))
  517. }
  518. }
  519. }
  520. }
  521. /// Signs in using the provided auth provider instance.
  522. ///
  523. /// Possible error codes:
  524. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  525. /// accounts are not enabled. Enable them in the Auth section of the
  526. /// Firebase console.
  527. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  528. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  529. /// sign in with an incorrect password.
  530. /// * `AuthErrorCodeWebNetworkRequestFailed` - Indicates that a network request within a
  531. /// SFSafariViewController or WKWebView failed.
  532. /// * `AuthErrorCodeWebInternalError` - Indicates that an internal error occurred within a
  533. /// SFSafariViewController or WKWebView.
  534. /// * `AuthErrorCodeWebSignInUserInteractionFailure` - Indicates a general failure during
  535. /// a web sign-in flow.
  536. /// * `AuthErrorCodeWebContextAlreadyPresented` - Indicates that an attempt was made to
  537. /// present a new web context while one was already being presented.
  538. /// * `AuthErrorCodeWebContextCancelled` - Indicates that the URL presentation was
  539. /// cancelled prematurely by the user.
  540. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  541. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  542. /// existing account, that cannot be authenticated with this sign-in method. Call
  543. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  544. /// the sign-in providers returned. This error will only be thrown if the "One account per
  545. /// email address" setting is enabled in the Firebase console, under Auth settings.
  546. /// - Parameter provider: An instance of an auth provider used to initiate the sign-in flow.
  547. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the AuthUIDelegate
  548. /// protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate
  549. /// will be used.
  550. /// - Returns: The `AuthDataResult` after the successful signin.
  551. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  552. @available(tvOS, unavailable)
  553. @available(macOS, unavailable)
  554. @available(watchOS, unavailable)
  555. @discardableResult
  556. open func signIn(with provider: FederatedAuthProvider,
  557. uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
  558. return try await withCheckedThrowingContinuation { continuation in
  559. self.signIn(with: provider, uiDelegate: uiDelegate) { result, error in
  560. if let result {
  561. continuation.resume(returning: result)
  562. } else {
  563. continuation.resume(throwing: error!)
  564. }
  565. }
  566. }
  567. }
  568. #endif // iOS
  569. /// Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook
  570. /// login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional
  571. /// identity provider data.
  572. ///
  573. /// Possible error codes:
  574. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  575. /// This could happen if it has expired or it is malformed.
  576. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts
  577. /// with the identity provider represented by the credential are not enabled.
  578. /// Enable them in the Auth section of the Firebase console.
  579. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  580. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  581. /// existing account, that cannot be authenticated with this sign-in method. Call
  582. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  583. /// the sign-in providers returned. This error will only be thrown if the "One account per
  584. /// email address" setting is enabled in the Firebase console, under Auth settings.
  585. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  586. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an
  587. /// incorrect password, if credential is of the type EmailPasswordAuthCredential.
  588. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  589. /// * `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was
  590. /// created with an empty verification ID.
  591. /// * `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential
  592. /// was created with an empty verification code.
  593. /// * `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential
  594. /// was created with an invalid verification Code.
  595. /// * `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was
  596. /// created with an invalid verification ID.
  597. /// * `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired.
  598. /// - Parameter credential: The credential supplied by the IdP.
  599. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  600. /// or is canceled. Invoked asynchronously on the main thread in the future.
  601. @objc(signInWithCredential:completion:)
  602. open func signIn(with credential: AuthCredential,
  603. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  604. kAuthGlobalWorkQueue.async {
  605. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  606. Task {
  607. do {
  608. let authData = try await self.internalSignInAndRetrieveData(withCredential: credential,
  609. isReauthentication: false)
  610. decoratedCallback(.success(authData))
  611. } catch {
  612. decoratedCallback(.failure(error))
  613. }
  614. }
  615. }
  616. }
  617. /// Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook
  618. /// login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional
  619. /// identity provider data.
  620. ///
  621. /// Possible error codes:
  622. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  623. /// This could happen if it has expired or it is malformed.
  624. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts
  625. /// with the identity provider represented by the credential are not enabled.
  626. /// Enable them in the Auth section of the Firebase console.
  627. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  628. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  629. /// existing account, that cannot be authenticated with this sign-in method. Call
  630. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  631. /// the sign-in providers returned. This error will only be thrown if the "One account per
  632. /// email address" setting is enabled in the Firebase console, under Auth settings.
  633. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  634. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an
  635. /// incorrect password, if credential is of the type EmailPasswordAuthCredential.
  636. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  637. /// * `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was
  638. /// created with an empty verification ID.
  639. /// * `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential
  640. /// was created with an empty verification code.
  641. /// * `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential
  642. /// was created with an invalid verification Code.
  643. /// * `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was
  644. /// created with an invalid verification ID.
  645. /// * `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired.
  646. /// - Parameter credential: The credential supplied by the IdP.
  647. /// - Returns: The `AuthDataResult` after the successful signin.
  648. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  649. @discardableResult
  650. open func signIn(with credential: AuthCredential) async throws -> AuthDataResult {
  651. return try await withCheckedThrowingContinuation { continuation in
  652. self.signIn(with: credential) { result, error in
  653. if let result {
  654. continuation.resume(returning: result)
  655. } else {
  656. continuation.resume(throwing: error!)
  657. }
  658. }
  659. }
  660. }
  661. /// Asynchronously creates and becomes an anonymous user.
  662. ///
  663. /// If there is already an anonymous user signed in, that user will be returned instead.
  664. /// If there is any other existing user signed in, that user will be signed out.
  665. ///
  666. /// Possible error codes:
  667. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are
  668. /// not enabled. Enable them in the Auth section of the Firebase console.
  669. /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is
  670. /// canceled. Invoked asynchronously on the main thread in the future.
  671. @objc open func signInAnonymously(completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  672. kAuthGlobalWorkQueue.async {
  673. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  674. if let currentUser = self._currentUser, currentUser.isAnonymous {
  675. let result = AuthDataResult(withUser: currentUser, additionalUserInfo: nil)
  676. decoratedCallback(.success(result))
  677. return
  678. }
  679. let request = SignUpNewUserRequest(requestConfiguration: self.requestConfiguration)
  680. Task {
  681. do {
  682. let response = try await self.backend.call(with: request)
  683. let user = try await self.completeSignIn(
  684. withAccessToken: response.idToken,
  685. accessTokenExpirationDate: response.approximateExpirationDate,
  686. refreshToken: response.refreshToken,
  687. anonymous: true
  688. )
  689. // TODO: The ObjC implementation passed a nil providerID to the nonnull providerID
  690. let additionalUserInfo = AdditionalUserInfo(providerID: "",
  691. profile: nil,
  692. username: nil,
  693. isNewUser: true)
  694. decoratedCallback(
  695. .success(AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo))
  696. )
  697. } catch {
  698. decoratedCallback(.failure(error))
  699. }
  700. }
  701. }
  702. }
  703. /// Asynchronously creates and becomes an anonymous user.
  704. ///
  705. /// If there is already an anonymous user signed in, that user will be returned instead.
  706. /// If there is any other existing user signed in, that user will be signed out.
  707. ///
  708. /// Possible error codes:
  709. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are
  710. /// not enabled. Enable them in the Auth section of the Firebase console.
  711. /// - Returns: The `AuthDataResult` after the successful signin.
  712. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  713. @discardableResult
  714. @objc open func signInAnonymously() async throws -> AuthDataResult {
  715. return try await withCheckedThrowingContinuation { continuation in
  716. self.signInAnonymously { result, error in
  717. if let result {
  718. continuation.resume(returning: result)
  719. } else {
  720. continuation.resume(throwing: error!)
  721. }
  722. }
  723. }
  724. }
  725. /// Asynchronously signs in to Firebase with the given Auth token.
  726. ///
  727. /// Possible error codes:
  728. /// * `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with
  729. /// the custom token.
  730. /// * `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key
  731. /// belong to different projects.
  732. /// - Parameter token: A self-signed custom auth token.
  733. /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is
  734. /// canceled. Invoked asynchronously on the main thread in the future.
  735. @objc open func signIn(withCustomToken token: String,
  736. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  737. kAuthGlobalWorkQueue.async {
  738. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  739. let request = VerifyCustomTokenRequest(token: token,
  740. requestConfiguration: self.requestConfiguration)
  741. Task {
  742. do {
  743. let response = try await self.backend.call(with: request)
  744. let user = try await self.completeSignIn(
  745. withAccessToken: response.idToken,
  746. accessTokenExpirationDate: response.approximateExpirationDate,
  747. refreshToken: response.refreshToken,
  748. anonymous: false
  749. )
  750. // TODO: The ObjC implementation passed a nil providerID to the nonnull providerID
  751. let additionalUserInfo = AdditionalUserInfo(providerID: "",
  752. profile: nil,
  753. username: nil,
  754. isNewUser: response.isNewUser)
  755. decoratedCallback(
  756. .success(AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo))
  757. )
  758. } catch {
  759. decoratedCallback(.failure(error))
  760. }
  761. }
  762. }
  763. }
  764. /// Asynchronously signs in to Firebase with the given Auth token.
  765. ///
  766. /// Possible error codes:
  767. /// * `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with
  768. /// the custom token.
  769. /// * `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key
  770. /// belong to different projects.
  771. /// - Parameter token: A self-signed custom auth token.
  772. /// - Returns: The `AuthDataResult` after the successful signin.
  773. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  774. @discardableResult
  775. open func signIn(withCustomToken token: String) async throws -> AuthDataResult {
  776. return try await withCheckedThrowingContinuation { continuation in
  777. self.signIn(withCustomToken: token) { result, error in
  778. if let result {
  779. continuation.resume(returning: result)
  780. } else {
  781. continuation.resume(throwing: error!)
  782. }
  783. }
  784. }
  785. }
  786. /// Creates and, on success, signs in a user with the given email address and password.
  787. ///
  788. /// Possible error codes:
  789. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  790. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up
  791. /// already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user
  792. /// used, and prompt the user to sign in with one of those.
  793. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts
  794. /// are not enabled. Enable them in the Auth section of the Firebase console.
  795. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  796. /// considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo
  797. /// dictionary object will contain more detailed explanation that can be shown to the user.
  798. /// - Parameter email: The user's email address.
  799. /// - Parameter password: The user's desired password.
  800. /// - Parameter completion: Optionally; a block which is invoked when the sign up flow finishes,
  801. /// or is canceled. Invoked asynchronously on the main thread in the future.
  802. @objc open func createUser(withEmail email: String,
  803. password: String,
  804. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  805. guard password.count > 0 else {
  806. if let completion {
  807. completion(nil, AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing password"))
  808. }
  809. return
  810. }
  811. guard email.count > 0 else {
  812. if let completion {
  813. completion(nil, AuthErrorUtils.missingEmailError(message: nil))
  814. }
  815. return
  816. }
  817. kAuthGlobalWorkQueue.async {
  818. let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion)
  819. let request = SignUpNewUserRequest(email: email,
  820. password: password,
  821. displayName: nil,
  822. idToken: nil,
  823. requestConfiguration: self.requestConfiguration)
  824. #if os(iOS)
  825. self.wrapInjectRecaptcha(request: request,
  826. action: AuthRecaptchaAction.signUpPassword) { response, error in
  827. if let error {
  828. DispatchQueue.main.async {
  829. decoratedCallback(.failure(error))
  830. }
  831. return
  832. }
  833. self.internalCreateUserWithEmail(request: request, inResponse: response,
  834. decoratedCallback: decoratedCallback)
  835. }
  836. #else
  837. self.internalCreateUserWithEmail(request: request, decoratedCallback: decoratedCallback)
  838. #endif
  839. }
  840. }
  841. func internalCreateUserWithEmail(request: SignUpNewUserRequest,
  842. inResponse: SignUpNewUserResponse? = nil,
  843. decoratedCallback: @escaping (Result<AuthDataResult, Error>)
  844. -> Void) {
  845. Task {
  846. do {
  847. var response: SignUpNewUserResponse
  848. if let inResponse {
  849. response = inResponse
  850. } else {
  851. response = try await self.backend.call(with: request)
  852. }
  853. let user = try await self.completeSignIn(
  854. withAccessToken: response.idToken,
  855. accessTokenExpirationDate: response.approximateExpirationDate,
  856. refreshToken: response.refreshToken,
  857. anonymous: false
  858. )
  859. let additionalUserInfo = AdditionalUserInfo(providerID: EmailAuthProvider.id,
  860. profile: nil,
  861. username: nil,
  862. isNewUser: true)
  863. decoratedCallback(
  864. .success(AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo))
  865. )
  866. } catch {
  867. decoratedCallback(.failure(error))
  868. }
  869. }
  870. }
  871. /// Creates and, on success, signs in a user with the given email address and password.
  872. ///
  873. /// Possible error codes:
  874. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  875. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up
  876. /// already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user
  877. /// used, and prompt the user to sign in with one of those.
  878. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts
  879. /// are not enabled. Enable them in the Auth section of the Firebase console.
  880. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  881. /// considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo
  882. /// dictionary object will contain more detailed explanation that can be shown to the user.
  883. /// - Parameter email: The user's email address.
  884. /// - Parameter password: The user's desired password.
  885. /// - Returns: The `AuthDataResult` after the successful signin.
  886. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  887. @discardableResult
  888. open func createUser(withEmail email: String, password: String) async throws -> AuthDataResult {
  889. return try await withCheckedThrowingContinuation { continuation in
  890. self.createUser(withEmail: email, password: password) { result, error in
  891. if let result {
  892. continuation.resume(returning: result)
  893. } else {
  894. continuation.resume(throwing: error!)
  895. }
  896. }
  897. }
  898. }
  899. /// Resets the password given a code sent to the user outside of the app and a new password
  900. /// for the user.
  901. ///
  902. /// Possible error codes:
  903. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  904. /// considered too weak.
  905. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign
  906. /// in with the specified identity provider.
  907. /// * `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired.
  908. /// * `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid.
  909. /// - Parameter code: The reset code.
  910. /// - Parameter newPassword: The new password.
  911. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  912. /// Invoked asynchronously on the main thread in the future.
  913. @objc open func confirmPasswordReset(withCode code: String, newPassword: String,
  914. completion: @escaping (Error?) -> Void) {
  915. kAuthGlobalWorkQueue.async {
  916. let request = ResetPasswordRequest(oobCode: code,
  917. newPassword: newPassword,
  918. requestConfiguration: self.requestConfiguration)
  919. self.wrapAsyncRPCTask(request, completion)
  920. }
  921. }
  922. /// Resets the password given a code sent to the user outside of the app and a new password
  923. /// for the user.
  924. ///
  925. /// Possible error codes:
  926. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  927. /// considered too weak.
  928. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign
  929. /// in with the specified identity provider.
  930. /// * `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired.
  931. /// * `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid.
  932. /// - Parameter code: The reset code.
  933. /// - Parameter newPassword: The new password.
  934. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  935. open func confirmPasswordReset(withCode code: String, newPassword: String) async throws {
  936. return try await withCheckedThrowingContinuation { continuation in
  937. self.confirmPasswordReset(withCode: code, newPassword: newPassword) { error in
  938. if let error {
  939. continuation.resume(throwing: error)
  940. } else {
  941. continuation.resume()
  942. }
  943. }
  944. }
  945. }
  946. /// Checks the validity of an out of band code.
  947. /// - Parameter code: The out of band code to check validity.
  948. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  949. /// Invoked
  950. /// asynchronously on the main thread in the future.
  951. @objc open func checkActionCode(_ code: String,
  952. completion: @escaping (ActionCodeInfo?, Error?) -> Void) {
  953. kAuthGlobalWorkQueue.async {
  954. let request = ResetPasswordRequest(oobCode: code,
  955. newPassword: nil,
  956. requestConfiguration: self.requestConfiguration)
  957. Task {
  958. do {
  959. let response = try await self.backend.call(with: request)
  960. let operation = ActionCodeInfo.actionCodeOperation(forRequestType: response.requestType)
  961. guard let email = response.email else {
  962. fatalError("Internal Auth Error: Failed to get a ResetPasswordResponse")
  963. }
  964. let actionCodeInfo = ActionCodeInfo(withOperation: operation,
  965. email: email,
  966. newEmail: response.verifiedEmail)
  967. Auth.wrapMainAsync(callback: completion, with: .success(actionCodeInfo))
  968. } catch {
  969. Auth.wrapMainAsync(callback: completion, with: .failure(error))
  970. }
  971. }
  972. }
  973. }
  974. /// Checks the validity of an out of band code.
  975. /// - Parameter code: The out of band code to check validity.
  976. /// - Returns: An `ActionCodeInfo`.
  977. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  978. open func checkActionCode(_ code: String) async throws -> ActionCodeInfo {
  979. return try await withCheckedThrowingContinuation { continuation in
  980. self.checkActionCode(code) { info, error in
  981. if let info {
  982. continuation.resume(returning: info)
  983. } else {
  984. continuation.resume(throwing: error!)
  985. }
  986. }
  987. }
  988. }
  989. /// Checks the validity of a verify password reset code.
  990. /// - Parameter code: The password reset code to be verified.
  991. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  992. /// Invoked asynchronously on the main thread in the future.
  993. @objc open func verifyPasswordResetCode(_ code: String,
  994. completion: @escaping (String?, Error?) -> Void) {
  995. checkActionCode(code) { info, error in
  996. if let error {
  997. completion(nil, error)
  998. return
  999. }
  1000. completion(info?.email, nil)
  1001. }
  1002. }
  1003. /// Checks the validity of a verify password reset code.
  1004. /// - Parameter code: The password reset code to be verified.
  1005. /// - Returns: An email.
  1006. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  1007. open func verifyPasswordResetCode(_ code: String) async throws -> String {
  1008. return try await withCheckedThrowingContinuation { continuation in
  1009. self.verifyPasswordResetCode(code) { code, error in
  1010. if let code {
  1011. continuation.resume(returning: code)
  1012. } else {
  1013. continuation.resume(throwing: error!)
  1014. }
  1015. }
  1016. }
  1017. }
  1018. /// Applies out of band code.
  1019. ///
  1020. /// This method will not work for out of band codes which require an additional parameter,
  1021. /// such as password reset code.
  1022. /// - Parameter code: The out of band code to be applied.
  1023. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  1024. /// Invoked asynchronously on the main thread in the future.
  1025. @objc open func applyActionCode(_ code: String, completion: @escaping (Error?) -> Void) {
  1026. kAuthGlobalWorkQueue.async {
  1027. let request = SetAccountInfoRequest(requestConfiguration: self.requestConfiguration)
  1028. request.oobCode = code
  1029. self.wrapAsyncRPCTask(request, completion)
  1030. }
  1031. }
  1032. /// Applies out of band code.
  1033. ///
  1034. /// This method will not work for out of band codes which require an additional parameter,
  1035. /// such as password reset code.
  1036. /// - Parameter code: The out of band code to be applied.
  1037. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  1038. open func applyActionCode(_ code: String) async throws {
  1039. return try await withCheckedThrowingContinuation { continuation in
  1040. self.applyActionCode(code) { error in
  1041. if let error {
  1042. continuation.resume(throwing: error)
  1043. } else {
  1044. continuation.resume()
  1045. }
  1046. }
  1047. }
  1048. }
  1049. /// Initiates a password reset for the given email address.
  1050. ///
  1051. /// This method does not throw an
  1052. /// error when there's no user account with the given email address and [Email Enumeration
  1053. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  1054. /// is enabled.
  1055. ///
  1056. /// Possible error codes:
  1057. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  1058. /// sent in the request.
  1059. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  1060. /// the console for this action.
  1061. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  1062. /// sending update email.
  1063. /// - Parameter email: The email address of the user.
  1064. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  1065. /// Invoked
  1066. /// asynchronously on the main thread in the future.
  1067. @objc open func sendPasswordReset(withEmail email: String,
  1068. completion: ((Error?) -> Void)? = nil) {
  1069. sendPasswordReset(withEmail: email, actionCodeSettings: nil, completion: completion)
  1070. }
  1071. /// Initiates a password reset for the given email address and `ActionCodeSettings` object.
  1072. ///
  1073. /// This method does not throw an
  1074. /// error when there's no user account with the given email address and [Email Enumeration
  1075. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  1076. /// is enabled.
  1077. ///
  1078. /// Possible error codes:
  1079. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  1080. /// sent in the request.
  1081. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  1082. /// the console for this action.
  1083. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  1084. /// sending update email.
  1085. /// * `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when
  1086. /// `handleCodeInApp` is set to true.
  1087. /// * `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name
  1088. /// is missing when the `androidInstallApp` flag is set to true.
  1089. /// * `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the
  1090. /// continue URL is not allowlisted in the Firebase console.
  1091. /// * `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the
  1092. /// continue URL is not valid.
  1093. /// - Parameter email: The email address of the user.
  1094. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  1095. /// handling action codes.
  1096. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  1097. /// Invoked asynchronously on the main thread in the future.
  1098. @objc open func sendPasswordReset(withEmail email: String,
  1099. actionCodeSettings: ActionCodeSettings?,
  1100. completion: ((Error?) -> Void)? = nil) {
  1101. kAuthGlobalWorkQueue.async {
  1102. let request = GetOOBConfirmationCodeRequest.passwordResetRequest(
  1103. email: email,
  1104. actionCodeSettings: actionCodeSettings,
  1105. requestConfiguration: self.requestConfiguration
  1106. )
  1107. #if os(iOS)
  1108. self.wrapInjectRecaptcha(request: request,
  1109. action: AuthRecaptchaAction.getOobCode) { result, error in
  1110. if let completion {
  1111. DispatchQueue.main.async {
  1112. completion(error)
  1113. }
  1114. }
  1115. }
  1116. #else
  1117. self.wrapAsyncRPCTask(request, completion)
  1118. #endif
  1119. }
  1120. }
  1121. /// Initiates a password reset for the given email address and `ActionCodeSettings` object.
  1122. ///
  1123. /// This method does not throw an
  1124. /// error when there's no user account with the given email address and [Email Enumeration
  1125. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  1126. /// is enabled.
  1127. ///
  1128. /// Possible error codes:
  1129. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  1130. /// sent in the request.
  1131. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  1132. /// the console for this action.
  1133. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  1134. /// sending update email.
  1135. /// * `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when
  1136. /// `handleCodeInApp` is set to true.
  1137. /// * `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name
  1138. /// is missing when the `androidInstallApp` flag is set to true.
  1139. /// * `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the
  1140. /// continue URL is not allowlisted in the Firebase console.
  1141. /// * `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the
  1142. /// continue URL is not valid.
  1143. /// - Parameter email: The email address of the user.
  1144. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  1145. /// handling action codes.
  1146. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  1147. open func sendPasswordReset(withEmail email: String,
  1148. actionCodeSettings: ActionCodeSettings? = nil) async throws {
  1149. return try await withCheckedThrowingContinuation { continuation in
  1150. self.sendPasswordReset(withEmail: email, actionCodeSettings: actionCodeSettings) { error in
  1151. if let error {
  1152. continuation.resume(throwing: error)
  1153. } else {
  1154. continuation.resume()
  1155. }
  1156. }
  1157. }
  1158. }
  1159. /// Sends a sign in with email link to provided email address.
  1160. /// - Parameter email: The email address of the user.
  1161. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  1162. /// handling action codes.
  1163. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  1164. /// Invoked asynchronously on the main thread in the future.
  1165. @objc open func sendSignInLink(toEmail email: String,
  1166. actionCodeSettings: ActionCodeSettings,
  1167. completion: ((Error?) -> Void)? = nil) {
  1168. if !actionCodeSettings.handleCodeInApp {
  1169. fatalError("The handleCodeInApp flag in ActionCodeSettings must be true for Email-link " +
  1170. "Authentication.")
  1171. }
  1172. kAuthGlobalWorkQueue.async {
  1173. let request = GetOOBConfirmationCodeRequest.signInWithEmailLinkRequest(
  1174. email,
  1175. actionCodeSettings: actionCodeSettings,
  1176. requestConfiguration: self.requestConfiguration
  1177. )
  1178. #if os(iOS)
  1179. self.wrapInjectRecaptcha(request: request,
  1180. action: AuthRecaptchaAction.getOobCode) { result, error in
  1181. if let completion {
  1182. DispatchQueue.main.async {
  1183. completion(error)
  1184. }
  1185. }
  1186. }
  1187. #else
  1188. self.wrapAsyncRPCTask(request, completion)
  1189. #endif
  1190. }
  1191. }
  1192. /// Sends a sign in with email link to provided email address.
  1193. /// - Parameter email: The email address of the user.
  1194. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  1195. /// handling action codes.
  1196. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  1197. open func sendSignInLink(toEmail email: String,
  1198. actionCodeSettings: ActionCodeSettings) async throws {
  1199. return try await withCheckedThrowingContinuation { continuation in
  1200. self.sendSignInLink(toEmail: email, actionCodeSettings: actionCodeSettings) { error in
  1201. if let error {
  1202. continuation.resume(throwing: error)
  1203. } else {
  1204. continuation.resume()
  1205. }
  1206. }
  1207. }
  1208. }
  1209. /// Signs out the current user.
  1210. ///
  1211. /// Possible error codes:
  1212. /// * `AuthErrorCodeKeychainError` - Indicates an error occurred when accessing the
  1213. /// keychain. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  1214. /// dictionary will contain more information about the error encountered.
  1215. @objc(signOut:) open func signOut() throws {
  1216. try kAuthGlobalWorkQueue.sync {
  1217. guard self._currentUser != nil else {
  1218. return
  1219. }
  1220. return try self.updateCurrentUser(nil, byForce: false, savingToDisk: true)
  1221. }
  1222. }
  1223. /// Checks if link is an email sign-in link.
  1224. /// - Parameter link: The email sign-in link.
  1225. /// - Returns: `true` when the link passed matches the expected format of an email sign-in link.
  1226. @objc open func isSignIn(withEmailLink link: String) -> Bool {
  1227. guard link.count > 0 else {
  1228. return false
  1229. }
  1230. let queryItems = getQueryItems(link)
  1231. if let _ = queryItems["oobCode"],
  1232. let mode = queryItems["mode"],
  1233. mode == "signIn" {
  1234. return true
  1235. }
  1236. return false
  1237. }
  1238. #if os(iOS) && !targetEnvironment(macCatalyst)
  1239. /// Initializes reCAPTCHA using the settings configured for the project or tenant.
  1240. ///
  1241. /// If you change the tenant ID of the `Auth` instance, the configuration will be
  1242. /// reloaded.
  1243. @objc(initializeRecaptchaConfigWithCompletion:)
  1244. open func initializeRecaptchaConfig(completion: ((Error?) -> Void)?) {
  1245. Task {
  1246. do {
  1247. try await initializeRecaptchaConfig()
  1248. if let completion {
  1249. completion(nil)
  1250. }
  1251. } catch {
  1252. if let completion {
  1253. completion(error)
  1254. }
  1255. }
  1256. }
  1257. }
  1258. /// Initializes reCAPTCHA using the settings configured for the project or tenant.
  1259. ///
  1260. /// If you change the tenant ID of the `Auth` instance, the configuration will be
  1261. /// reloaded.
  1262. open func initializeRecaptchaConfig() async throws {
  1263. // Trigger recaptcha verification flow to initialize the recaptcha client and
  1264. // config. Recaptcha token will be returned.
  1265. let verifier = AuthRecaptchaVerifier.shared(auth: self)
  1266. _ = try await verifier.verify(forceRefresh: true, action: AuthRecaptchaAction.defaultAction)
  1267. }
  1268. #endif
  1269. /// Registers a block as an "auth state did change" listener.
  1270. ///
  1271. /// To be invoked when:
  1272. /// * The block is registered as a listener,
  1273. /// * A user with a different UID from the current user has signed in, or
  1274. /// * The current user has signed out.
  1275. ///
  1276. /// The block is invoked immediately after adding it according to its standard invocation
  1277. /// semantics, asynchronously on the main thread. Users should pay special attention to
  1278. /// making sure the block does not inadvertently retain objects which should not be retained by
  1279. /// the long-lived block. The block itself will be retained by `Auth` until it is
  1280. /// unregistered or until the `Auth` instance is otherwise deallocated.
  1281. /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on
  1282. /// the main thread, even for it's initial invocation after having been added as a listener.
  1283. /// - Returns: A handle useful for manually unregistering the block as a listener.
  1284. @objc(addAuthStateDidChangeListener:)
  1285. open func addStateDidChangeListener(_ listener: @escaping (Auth, User?) -> Void)
  1286. -> NSObjectProtocol {
  1287. var firstInvocation = true
  1288. var previousUserID: String?
  1289. return addIDTokenDidChangeListener { auth, user in
  1290. let shouldCallListener = firstInvocation || previousUserID != user?.uid
  1291. firstInvocation = false
  1292. previousUserID = user?.uid
  1293. if shouldCallListener {
  1294. listener(auth, user)
  1295. }
  1296. }
  1297. }
  1298. /// Unregisters a block as an "auth state did change" listener.
  1299. /// - Parameter listenerHandle: The handle for the listener.
  1300. @objc(removeAuthStateDidChangeListener:)
  1301. open func removeStateDidChangeListener(_ listenerHandle: NSObjectProtocol) {
  1302. NotificationCenter.default.removeObserver(listenerHandle)
  1303. objc_sync_enter(Auth.self)
  1304. defer { objc_sync_exit(Auth.self) }
  1305. listenerHandles.remove(listenerHandle)
  1306. }
  1307. /// Registers a block as an "ID token did change" listener.
  1308. ///
  1309. /// To be invoked when:
  1310. /// * The block is registered as a listener,
  1311. /// * A user with a different UID from the current user has signed in,
  1312. /// * The ID token of the current user has been refreshed, or
  1313. /// * The current user has signed out.
  1314. ///
  1315. /// The block is invoked immediately after adding it according to its standard invocation
  1316. /// semantics, asynchronously on the main thread. Users should pay special attention to
  1317. /// making sure the block does not inadvertently retain objects which should not be retained by
  1318. /// the long-lived block. The block itself will be retained by `Auth` until it is
  1319. /// unregistered or until the `Auth` instance is otherwise deallocated.
  1320. /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on
  1321. /// the main thread, even for it's initial invocation after having been added as a listener.
  1322. /// - Returns: A handle useful for manually unregistering the block as a listener.
  1323. @objc open func addIDTokenDidChangeListener(_ listener: @escaping (Auth, User?) -> Void)
  1324. -> NSObjectProtocol {
  1325. let handle = NotificationCenter.default.addObserver(
  1326. forName: Auth.authStateDidChangeNotification,
  1327. object: self,
  1328. queue: OperationQueue.main
  1329. ) { notification in
  1330. if let auth = notification.object as? Auth {
  1331. listener(auth, auth._currentUser)
  1332. }
  1333. }
  1334. objc_sync_enter(Auth.self)
  1335. listenerHandles.add(listener)
  1336. objc_sync_exit(Auth.self)
  1337. DispatchQueue.main.async {
  1338. listener(self, self._currentUser)
  1339. }
  1340. return handle
  1341. }
  1342. /// Unregisters a block as an "ID token did change" listener.
  1343. /// - Parameter listenerHandle: The handle for the listener.
  1344. @objc open func removeIDTokenDidChangeListener(_ listenerHandle: NSObjectProtocol) {
  1345. NotificationCenter.default.removeObserver(listenerHandle)
  1346. objc_sync_enter(Auth.self)
  1347. listenerHandles.remove(listenerHandle)
  1348. objc_sync_exit(Auth.self)
  1349. }
  1350. /// Sets `languageCode` to the app's current language.
  1351. @objc open func useAppLanguage() {
  1352. kAuthGlobalWorkQueue.sync {
  1353. self.requestConfiguration.languageCode = Locale.preferredLanguages.first
  1354. }
  1355. }
  1356. /// Configures Firebase Auth to connect to an emulated host instead of the remote backend.
  1357. @objc open func useEmulator(withHost host: String, port: Int) {
  1358. guard host.count > 0 else {
  1359. fatalError("Cannot connect to empty host")
  1360. }
  1361. // If host is an IPv6 address, it should be formatted with surrounding brackets.
  1362. let formattedHost = host.contains(":") ? "[\(host)]" : host
  1363. kAuthGlobalWorkQueue.sync {
  1364. self.requestConfiguration.emulatorHostAndPort = "\(formattedHost):\(port)"
  1365. #if os(iOS)
  1366. self.settings?.appVerificationDisabledForTesting = true
  1367. #endif
  1368. }
  1369. }
  1370. /// Revoke the users token with authorization code.
  1371. /// - Parameter authorizationCode: The authorization code used to perform the revocation.
  1372. /// - Parameter completion: (Optional) the block invoked when the request to revoke the token is
  1373. /// complete, or fails. Invoked asynchronously on the main thread in the future.
  1374. @objc open func revokeToken(withAuthorizationCode authorizationCode: String,
  1375. completion: ((Error?) -> Void)? = nil) {
  1376. _currentUser?.internalGetToken(backend: backend) { idToken, error in
  1377. if let error {
  1378. Auth.wrapMainAsync(completion, error)
  1379. return
  1380. }
  1381. guard let idToken else {
  1382. fatalError("Internal Auth Error: Both idToken and error are nil")
  1383. }
  1384. let request = RevokeTokenRequest(withToken: authorizationCode,
  1385. idToken: idToken,
  1386. requestConfiguration: self.requestConfiguration)
  1387. self.wrapAsyncRPCTask(request, completion)
  1388. }
  1389. }
  1390. /// Revoke the users token with authorization code.
  1391. /// - Parameter authorizationCode: The authorization code used to perform the revocation.
  1392. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  1393. open func revokeToken(withAuthorizationCode authorizationCode: String) async throws {
  1394. return try await withCheckedThrowingContinuation { continuation in
  1395. self.revokeToken(withAuthorizationCode: authorizationCode) { error in
  1396. if let error {
  1397. continuation.resume(throwing: error)
  1398. } else {
  1399. continuation.resume()
  1400. }
  1401. }
  1402. }
  1403. }
  1404. /// Switch userAccessGroup and current user to the given accessGroup and the user stored in it.
  1405. @objc open func useUserAccessGroup(_ accessGroup: String?) throws {
  1406. // self.storedUserManager is initialized asynchronously. Make sure it is done.
  1407. kAuthGlobalWorkQueue.sync {}
  1408. return try internalUseUserAccessGroup(accessGroup)
  1409. }
  1410. private func internalUseUserAccessGroup(_ accessGroup: String?) throws {
  1411. storedUserManager.setStoredUserAccessGroup(accessGroup: accessGroup)
  1412. let user = try getStoredUser(forAccessGroup: accessGroup)
  1413. try updateCurrentUser(user, byForce: false, savingToDisk: false)
  1414. if userAccessGroup == nil, accessGroup != nil {
  1415. let userKey = "\(firebaseAppName)\(kUserKey)"
  1416. try keychainServices.removeData(forKey: userKey)
  1417. }
  1418. userAccessGroup = accessGroup
  1419. lastNotifiedUserToken = user?.rawAccessToken()
  1420. }
  1421. /// Get the stored user in the given accessGroup.
  1422. ///
  1423. /// This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`.
  1424. /// and will return `nil`.
  1425. /// Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details.
  1426. @available(swift 1000.0) // Objective-C only API
  1427. @objc(getStoredUserForAccessGroup:error:)
  1428. open func __getStoredUser(forAccessGroup accessGroup: String?,
  1429. error outError: NSErrorPointer) -> User? {
  1430. do {
  1431. return try getStoredUser(forAccessGroup: accessGroup)
  1432. } catch {
  1433. outError?.pointee = error as NSError
  1434. return nil
  1435. }
  1436. }
  1437. /// Get the stored user in the given accessGroup.
  1438. ///
  1439. /// This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`.
  1440. /// and will return `nil`.
  1441. ///
  1442. /// Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details.
  1443. open func getStoredUser(forAccessGroup accessGroup: String?) throws -> User? {
  1444. var user: User?
  1445. if let accessGroup {
  1446. #if os(tvOS)
  1447. if shareAuthStateAcrossDevices {
  1448. AuthLog.logError(code: "I-AUT000001",
  1449. message: "Getting a stored user for a given access group is not supported " +
  1450. "on tvOS when `shareAuthStateAcrossDevices` is set to `true` (#8878)." +
  1451. "This case will return `nil`.")
  1452. return nil
  1453. }
  1454. #endif
  1455. guard let apiKey = app?.options.apiKey else {
  1456. fatalError("Internal Auth Error: missing apiKey")
  1457. }
  1458. user = try storedUserManager.getStoredUser(
  1459. accessGroup: accessGroup,
  1460. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1461. projectIdentifier: apiKey
  1462. )
  1463. } else {
  1464. let userKey = "\(firebaseAppName)\(kUserKey)"
  1465. if let encodedUserData = try keychainServices.data(forKey: userKey) {
  1466. let unarchiver = try NSKeyedUnarchiver(forReadingFrom: encodedUserData)
  1467. user = unarchiver.decodeObject(of: User.self, forKey: userKey)
  1468. }
  1469. }
  1470. user?.auth = self
  1471. return user
  1472. }
  1473. #if os(iOS)
  1474. /// The APNs token used for phone number authentication.
  1475. ///
  1476. /// The type of the token (production or sandbox) will be automatically
  1477. /// detected based on your provisioning profile.
  1478. ///
  1479. /// This property is available on iOS only.
  1480. ///
  1481. /// If swizzling is disabled, the APNs Token must be set for phone number auth to work,
  1482. /// by either setting this property or by calling `setAPNSToken(_:type:)`.
  1483. @objc(APNSToken) open var apnsToken: Data? {
  1484. kAuthGlobalWorkQueue.sync {
  1485. self.tokenManager.token?.data
  1486. }
  1487. }
  1488. /// Sets the APNs token along with its type.
  1489. ///
  1490. /// This method is available on iOS only.
  1491. ///
  1492. /// If swizzling is disabled, the APNs Token must be set for phone number auth to work,
  1493. /// by either setting calling this method or by setting the `APNSToken` property.
  1494. @objc open func setAPNSToken(_ token: Data, type: AuthAPNSTokenType) {
  1495. kAuthGlobalWorkQueue.sync {
  1496. self.tokenManager.token = AuthAPNSToken(withData: token, type: type)
  1497. }
  1498. }
  1499. /// Whether the specific remote notification is handled by `Auth` .
  1500. ///
  1501. /// This method is available on iOS only.
  1502. ///
  1503. /// If swizzling is disabled, related remote notifications must be forwarded to this method
  1504. /// for phone number auth to work.
  1505. /// - Parameter userInfo: A dictionary that contains information related to the
  1506. /// notification in question.
  1507. /// - Returns: Whether or the notification is handled. A return value of `true` means the
  1508. /// notification is for Firebase Auth so the caller should ignore the notification from further
  1509. /// processing, and `false` means the notification is for the app (or another library) so
  1510. /// the caller should continue handling this notification as usual.
  1511. @objc open func canHandleNotification(_ userInfo: [AnyHashable: Any]) -> Bool {
  1512. kAuthGlobalWorkQueue.sync {
  1513. self.notificationManager.canHandle(notification: userInfo)
  1514. }
  1515. }
  1516. /// Whether the specific URL is handled by `Auth` .
  1517. ///
  1518. /// This method is available on iOS only.
  1519. ///
  1520. /// If swizzling is disabled, URLs received by the application delegate must be forwarded
  1521. /// to this method for phone number auth to work.
  1522. /// - Parameter url: The URL received by the application delegate from any of the openURL
  1523. /// method.
  1524. /// - Returns: Whether or the URL is handled. `true` means the URL is for Firebase Auth
  1525. /// so the caller should ignore the URL from further processing, and `false` means the
  1526. /// the URL is for the app (or another library) so the caller should continue handling
  1527. /// this URL as usual.
  1528. @objc(canHandleURL:) open func canHandle(_ url: URL) -> Bool {
  1529. kAuthGlobalWorkQueue.sync {
  1530. guard let authURLPresenter = self.authURLPresenter as? AuthURLPresenter else {
  1531. return false
  1532. }
  1533. return authURLPresenter.canHandle(url: url)
  1534. }
  1535. }
  1536. #endif
  1537. /// The name of the `NSNotificationCenter` notification which is posted when the auth state
  1538. /// changes (for example, a new token has been produced, a user signs in or signs out).
  1539. ///
  1540. /// The object parameter of the notification is the sender `Auth` instance.
  1541. public static let authStateDidChangeNotification =
  1542. NSNotification.Name(rawValue: "FIRAuthStateDidChangeNotification")
  1543. // MARK: Internal methods
  1544. init(app: FirebaseApp,
  1545. keychainStorageProvider: AuthKeychainStorage = AuthKeychainStorageReal(),
  1546. backend: AuthBackend = .init(rpcIssuer: AuthBackendRPCIssuer()),
  1547. authDispatcher: AuthDispatcher = .init()) {
  1548. self.app = app
  1549. mainBundleUrlTypes = Bundle.main
  1550. .object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]]
  1551. let appCheck = ComponentType<AppCheckInterop>.instance(for: AppCheckInterop.self,
  1552. in: app.container)
  1553. guard let apiKey = app.options.apiKey else {
  1554. fatalError("Missing apiKey for Auth initialization")
  1555. }
  1556. firebaseAppName = app.name
  1557. #if os(iOS)
  1558. authURLPresenter = AuthURLPresenter()
  1559. settings = AuthSettings()
  1560. GULAppDelegateSwizzler.proxyOriginalDelegateIncludingAPNSMethods()
  1561. GULSceneDelegateSwizzler.proxyOriginalSceneDelegate()
  1562. #endif
  1563. requestConfiguration = AuthRequestConfiguration(apiKey: apiKey,
  1564. appID: app.options.googleAppID,
  1565. auth: nil,
  1566. heartbeatLogger: app.heartbeatLogger,
  1567. appCheck: appCheck)
  1568. self.backend = backend
  1569. self.authDispatcher = authDispatcher
  1570. let keychainServiceName = Auth.keychainServiceName(for: app)
  1571. keychainServices = AuthKeychainServices(service: keychainServiceName,
  1572. storage: keychainStorageProvider)
  1573. storedUserManager = AuthStoredUserManager(
  1574. serviceName: keychainServiceName,
  1575. keychainServices: keychainServices
  1576. )
  1577. super.init()
  1578. requestConfiguration.auth = self
  1579. protectedDataInitialization()
  1580. }
  1581. private func protectedDataInitialization() {
  1582. // Continue with the rest of initialization in the work thread.
  1583. kAuthGlobalWorkQueue.async { [weak self] in
  1584. // Load current user from Keychain.
  1585. guard let self else {
  1586. return
  1587. }
  1588. do {
  1589. if let storedUserAccessGroup = self.storedUserManager.getStoredUserAccessGroup() {
  1590. try self.internalUseUserAccessGroup(storedUserAccessGroup)
  1591. } else {
  1592. let user = try self.getUser()
  1593. if let user {
  1594. self.tenantID = user.tenantID
  1595. }
  1596. try self.updateCurrentUser(user, byForce: false, savingToDisk: false)
  1597. if let user {
  1598. self.lastNotifiedUserToken = user.rawAccessToken()
  1599. }
  1600. }
  1601. } catch {
  1602. #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
  1603. if (error as NSError).code == AuthErrorCode.keychainError.rawValue {
  1604. // If there's a keychain error, assume it is due to the keychain being accessed
  1605. // before the device is unlocked as a result of prewarming, and listen for the
  1606. // UIApplicationProtectedDataDidBecomeAvailable notification.
  1607. self.addProtectedDataDidBecomeAvailableObserver()
  1608. }
  1609. #endif
  1610. AuthLog.logError(code: "I-AUT000001",
  1611. message: "Error loading saved user when starting up: \(error)")
  1612. }
  1613. #if os(iOS)
  1614. if GULAppEnvironmentUtil.isAppExtension() {
  1615. // iOS App extensions should not call [UIApplication sharedApplication], even if
  1616. // UIApplication responds to it.
  1617. return
  1618. }
  1619. // Using reflection here to avoid build errors in extensions.
  1620. let sel = NSSelectorFromString("sharedApplication")
  1621. guard UIApplication.responds(to: sel),
  1622. let rawApplication = UIApplication.perform(sel),
  1623. let application = rawApplication.takeUnretainedValue() as? UIApplication else {
  1624. return
  1625. }
  1626. // Initialize for phone number auth.
  1627. self.tokenManager = AuthAPNSTokenManager(withApplication: application)
  1628. self.appCredentialManager = AuthAppCredentialManager(withKeychain: self.keychainServices)
  1629. self.notificationManager = AuthNotificationManager(
  1630. withApplication: application,
  1631. appCredentialManager: self.appCredentialManager
  1632. )
  1633. GULAppDelegateSwizzler.registerAppDelegateInterceptor(self)
  1634. GULSceneDelegateSwizzler.registerSceneDelegateInterceptor(self)
  1635. #endif
  1636. }
  1637. }
  1638. #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
  1639. private func addProtectedDataDidBecomeAvailableObserver() {
  1640. protectedDataDidBecomeAvailableObserver =
  1641. NotificationCenter.default.addObserver(
  1642. forName: UIApplication.protectedDataDidBecomeAvailableNotification,
  1643. object: nil,
  1644. queue: nil
  1645. ) { [weak self] notification in
  1646. guard let self else { return }
  1647. if let observer = self.protectedDataDidBecomeAvailableObserver {
  1648. NotificationCenter.default.removeObserver(
  1649. observer,
  1650. name: UIApplication.protectedDataDidBecomeAvailableNotification,
  1651. object: nil
  1652. )
  1653. }
  1654. self.protectedDataInitialization()
  1655. }
  1656. }
  1657. #endif
  1658. deinit {
  1659. let defaultCenter = NotificationCenter.default
  1660. while listenerHandles.count > 0 {
  1661. let handleToRemove = listenerHandles.lastObject
  1662. defaultCenter.removeObserver(handleToRemove as Any)
  1663. listenerHandles.removeLastObject()
  1664. }
  1665. #if os(iOS)
  1666. defaultCenter.removeObserver(applicationDidBecomeActiveObserver as Any,
  1667. name: UIApplication.didBecomeActiveNotification,
  1668. object: nil)
  1669. defaultCenter.removeObserver(applicationDidEnterBackgroundObserver as Any,
  1670. name: UIApplication.didEnterBackgroundNotification,
  1671. object: nil)
  1672. #endif
  1673. }
  1674. private func getUser() throws -> User? {
  1675. var user: User?
  1676. if let userAccessGroup {
  1677. guard let apiKey = app?.options.apiKey else {
  1678. fatalError("Internal Auth Error: missing apiKey")
  1679. }
  1680. user = try storedUserManager.getStoredUser(
  1681. accessGroup: userAccessGroup,
  1682. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1683. projectIdentifier: apiKey
  1684. )
  1685. } else {
  1686. let userKey = "\(firebaseAppName)\(kUserKey)"
  1687. guard let encodedUserData = try keychainServices.data(forKey: userKey) else {
  1688. return nil
  1689. }
  1690. let unarchiver = try NSKeyedUnarchiver(forReadingFrom: encodedUserData)
  1691. user = unarchiver.decodeObject(of: User.self, forKey: userKey)
  1692. }
  1693. user?.auth = self
  1694. return user
  1695. }
  1696. /// Gets the keychain service name global data for the particular app by name.
  1697. /// - Parameter appName: The name of the Firebase app to get keychain service name for.
  1698. class func keychainServiceForAppID(_ appID: String) -> String {
  1699. return "firebase_auth_\(appID)"
  1700. }
  1701. func updateKeychain(withUser user: User?) -> Error? {
  1702. if user != _currentUser {
  1703. // No-op if the user is no longer signed in. This is not considered an error as we don't check
  1704. // whether the user is still current on other callbacks of user operations either.
  1705. return nil
  1706. }
  1707. do {
  1708. try saveUser(user)
  1709. possiblyPostAuthStateChangeNotification()
  1710. } catch {
  1711. return error
  1712. }
  1713. return nil
  1714. }
  1715. /// A map from Firebase app name to keychain service names.
  1716. ///
  1717. /// This map is needed for looking up the keychain service name after the FirebaseApp instance
  1718. /// is deleted, to remove the associated keychain item. Accessing should occur within a
  1719. /// @synchronized([FIRAuth class]) context.
  1720. fileprivate static var gKeychainServiceNameForAppName: [String: String] = [:]
  1721. /// Gets the keychain service name global data for the particular app by
  1722. /// name, creating an entry for one if it does not exist.
  1723. /// - Parameter app: The Firebase app to get the keychain service name for.
  1724. /// - Returns: The keychain service name for the given app.
  1725. static func keychainServiceName(for app: FirebaseApp) -> String {
  1726. objc_sync_enter(Auth.self)
  1727. defer { objc_sync_exit(Auth.self) }
  1728. let appName = app.name
  1729. if let serviceName = gKeychainServiceNameForAppName[appName] {
  1730. return serviceName
  1731. } else {
  1732. let serviceName = "firebase_auth_\(app.options.googleAppID)"
  1733. gKeychainServiceNameForAppName[appName] = serviceName
  1734. return serviceName
  1735. }
  1736. }
  1737. /// Deletes the keychain service name global data for the particular app by name.
  1738. /// - Parameter appName: The name of the Firebase app to delete keychain service name for.
  1739. /// - Returns: The deleted keychain service name, if any.
  1740. static func deleteKeychainServiceNameForAppName(_ appName: String) -> String? {
  1741. objc_sync_enter(Auth.self)
  1742. defer { objc_sync_exit(Auth.self) }
  1743. guard let serviceName = gKeychainServiceNameForAppName[appName] else {
  1744. return nil
  1745. }
  1746. gKeychainServiceNameForAppName.removeValue(forKey: appName)
  1747. return serviceName
  1748. }
  1749. func signOutByForce(withUserID userID: String) throws {
  1750. guard _currentUser?.uid == userID else {
  1751. return
  1752. }
  1753. try updateCurrentUser(nil, byForce: true, savingToDisk: true)
  1754. }
  1755. // MARK: Private methods
  1756. /// Posts the auth state change notification if current user's token has been changed.
  1757. private func possiblyPostAuthStateChangeNotification() {
  1758. let token = _currentUser?.rawAccessToken()
  1759. if lastNotifiedUserToken == token ||
  1760. (token != nil && lastNotifiedUserToken == token) {
  1761. return
  1762. }
  1763. lastNotifiedUserToken = token
  1764. if autoRefreshTokens {
  1765. // Schedule new refresh task after successful attempt.
  1766. scheduleAutoTokenRefresh()
  1767. }
  1768. var internalNotificationParameters: [String: Any] = [:]
  1769. if let app = app {
  1770. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = app
  1771. }
  1772. if let token, token.count > 0 {
  1773. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token
  1774. }
  1775. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = _currentUser?
  1776. .uid
  1777. let notifications = NotificationCenter.default
  1778. DispatchQueue.main.async {
  1779. notifications.post(name: NSNotification.Name.FIRAuthStateDidChangeInternal,
  1780. object: self,
  1781. userInfo: internalNotificationParameters)
  1782. notifications.post(name: Auth.authStateDidChangeNotification, object: self)
  1783. }
  1784. }
  1785. /// Schedules a task to automatically refresh tokens on the current user. The0 token refresh
  1786. /// is scheduled 5 minutes before the scheduled expiration time.
  1787. ///
  1788. /// If the token expires in less than 5 minutes, schedule the token refresh immediately.
  1789. private func scheduleAutoTokenRefresh() {
  1790. let tokenExpirationInterval =
  1791. (_currentUser?.accessTokenExpirationDate()?.timeIntervalSinceNow ?? 0) - 5 * 60
  1792. scheduleAutoTokenRefresh(withDelay: max(tokenExpirationInterval, 0), retry: false)
  1793. }
  1794. /// Schedules a task to automatically refresh tokens on the current user.
  1795. /// - Parameter delay: The delay in seconds after which the token refresh task should be scheduled
  1796. /// to be executed.
  1797. /// - Parameter retry: Flag to determine whether the invocation is a retry attempt or not.
  1798. private func scheduleAutoTokenRefresh(withDelay delay: TimeInterval, retry: Bool) {
  1799. guard let accessToken = _currentUser?.rawAccessToken() else {
  1800. return
  1801. }
  1802. let intDelay = Int(ceil(delay))
  1803. if retry {
  1804. AuthLog.logInfo(code: "I-AUT000003", message: "Token auto-refresh re-scheduled in " +
  1805. "\(intDelay / 60):\(intDelay % 60) " +
  1806. "because of error on previous refresh attempt.")
  1807. } else {
  1808. AuthLog.logInfo(code: "I-AUT000004", message: "Token auto-refresh scheduled in " +
  1809. "\(intDelay / 60):\(intDelay % 60) " +
  1810. "for the new token.")
  1811. }
  1812. autoRefreshScheduled = true
  1813. weak var weakSelf = self
  1814. authDispatcher.dispatch(afterDelay: delay, queue: kAuthGlobalWorkQueue) {
  1815. guard let strongSelf = weakSelf else {
  1816. return
  1817. }
  1818. guard strongSelf._currentUser?.rawAccessToken() == accessToken else {
  1819. // Another auto refresh must have been scheduled, so keep _autoRefreshScheduled unchanged.
  1820. return
  1821. }
  1822. strongSelf.autoRefreshScheduled = false
  1823. if strongSelf.isAppInBackground {
  1824. return
  1825. }
  1826. let uid = strongSelf._currentUser?.uid
  1827. strongSelf._currentUser?
  1828. .internalGetToken(forceRefresh: true, backend: strongSelf.backend) { token, error in
  1829. if strongSelf._currentUser?.uid != uid {
  1830. return
  1831. }
  1832. if error != nil {
  1833. // Kicks off exponential back off logic to retry failed attempt. Starts with one minute
  1834. // delay (60 seconds) if this is the first failed attempt.
  1835. let rescheduleDelay = retry ? min(delay * 2, 16 * 60) : 60
  1836. strongSelf.scheduleAutoTokenRefresh(withDelay: rescheduleDelay, retry: true)
  1837. }
  1838. }
  1839. }
  1840. }
  1841. /// Update the current user; initializing the user's internal properties correctly, and
  1842. /// optionally saving the user to disk.
  1843. ///
  1844. /// This method is called during: sign in and sign out events, as well as during class
  1845. /// initialization time. The only time the saveToDisk parameter should be set to NO is during
  1846. /// class initialization time because the user was just read from disk.
  1847. /// - Parameter user: The user to use as the current user (including nil, which is passed at sign
  1848. /// out time.)
  1849. /// - Parameter saveToDisk: Indicates the method should persist the user data to disk.
  1850. func updateCurrentUser(_ user: User?, byForce force: Bool,
  1851. savingToDisk saveToDisk: Bool) throws {
  1852. if user == _currentUser {
  1853. possiblyPostAuthStateChangeNotification()
  1854. }
  1855. if let user {
  1856. if user.tenantID != nil || tenantID != nil, tenantID != user.tenantID {
  1857. throw AuthErrorUtils.tenantIDMismatchError()
  1858. }
  1859. }
  1860. var throwError: Error?
  1861. if saveToDisk {
  1862. do {
  1863. try saveUser(user)
  1864. } catch {
  1865. throwError = error
  1866. }
  1867. }
  1868. if throwError == nil || force {
  1869. _currentUser = user
  1870. possiblyPostAuthStateChangeNotification()
  1871. }
  1872. if let throwError {
  1873. throw throwError
  1874. }
  1875. }
  1876. private func saveUser(_ user: User?) throws {
  1877. if let userAccessGroup {
  1878. guard let apiKey = app?.options.apiKey else {
  1879. fatalError("Internal Auth Error: Missing apiKey in saveUser")
  1880. }
  1881. if let user {
  1882. try storedUserManager.setStoredUser(user: user,
  1883. accessGroup: userAccessGroup,
  1884. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1885. projectIdentifier: apiKey)
  1886. } else {
  1887. try storedUserManager.removeStoredUser(
  1888. accessGroup: userAccessGroup,
  1889. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1890. projectIdentifier: apiKey
  1891. )
  1892. }
  1893. } else {
  1894. let userKey = "\(firebaseAppName)\(kUserKey)"
  1895. if let user {
  1896. let archiver = NSKeyedArchiver(requiringSecureCoding: true)
  1897. archiver.encode(user, forKey: userKey)
  1898. archiver.finishEncoding()
  1899. let archiveData = archiver.encodedData
  1900. // Save the user object's encoded value.
  1901. try keychainServices.setData(archiveData as Data, forKey: userKey)
  1902. } else {
  1903. try keychainServices.removeData(forKey: userKey)
  1904. }
  1905. }
  1906. }
  1907. /// Completes a sign-in flow once we have access and refresh tokens for the user.
  1908. /// - Parameter accessToken: The STS access token.
  1909. /// - Parameter accessTokenExpirationDate: The approximate expiration date of the access token.
  1910. /// - Parameter refreshToken: The STS refresh token.
  1911. /// - Parameter anonymous: Whether or not the user is anonymous.
  1912. @discardableResult
  1913. func completeSignIn(withAccessToken accessToken: String?,
  1914. accessTokenExpirationDate: Date?,
  1915. refreshToken: String?,
  1916. anonymous: Bool) async throws -> User {
  1917. return try await User.retrieveUser(withAuth: self,
  1918. accessToken: accessToken,
  1919. accessTokenExpirationDate: accessTokenExpirationDate,
  1920. refreshToken: refreshToken,
  1921. anonymous: anonymous)
  1922. }
  1923. /// Signs in using an email address and password.
  1924. ///
  1925. /// This is the internal counterpart of this method, which uses a callback that does not
  1926. /// update the current user.
  1927. /// - Parameter email: The user's email address.
  1928. /// - Parameter password: The user's password.
  1929. private func internalSignInAndRetrieveData(withEmail email: String,
  1930. password: String) async throws -> AuthDataResult {
  1931. let credential = EmailAuthCredential(withEmail: email, password: password)
  1932. return try await internalSignInAndRetrieveData(withCredential: credential,
  1933. isReauthentication: false)
  1934. }
  1935. func internalSignInAndRetrieveData(withCredential credential: AuthCredential,
  1936. isReauthentication: Bool) async throws
  1937. -> AuthDataResult {
  1938. if let emailCredential = credential as? EmailAuthCredential {
  1939. // Special case for email/password credentials
  1940. switch emailCredential.emailType {
  1941. case let .link(link):
  1942. // Email link sign in
  1943. return try await internalSignInAndRetrieveData(withEmail: emailCredential.email, link: link)
  1944. case let .password(password):
  1945. // Email password sign in
  1946. let user = try await internalSignInUser(
  1947. withEmail: emailCredential.email,
  1948. password: password
  1949. )
  1950. let additionalUserInfo = AdditionalUserInfo(providerID: EmailAuthProvider.id,
  1951. profile: nil,
  1952. username: nil,
  1953. isNewUser: false)
  1954. return AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo)
  1955. }
  1956. }
  1957. #if !os(watchOS)
  1958. if let gameCenterCredential = credential as? GameCenterAuthCredential {
  1959. return try await signInAndRetrieveData(withGameCenterCredential: gameCenterCredential)
  1960. }
  1961. #endif
  1962. #if os(iOS)
  1963. if let phoneCredential = credential as? PhoneAuthCredential {
  1964. // Special case for phone auth credentials
  1965. let operation = isReauthentication ? AuthOperationType.reauth :
  1966. AuthOperationType.signUpOrSignIn
  1967. let response = try await signIn(withPhoneCredential: phoneCredential,
  1968. operation: operation)
  1969. let user = try await completeSignIn(withAccessToken: response.idToken,
  1970. accessTokenExpirationDate: response
  1971. .approximateExpirationDate,
  1972. refreshToken: response.refreshToken,
  1973. anonymous: false)
  1974. let additionalUserInfo = AdditionalUserInfo(providerID: PhoneAuthProvider.id,
  1975. profile: nil,
  1976. username: nil,
  1977. isNewUser: response.isNewUser)
  1978. return AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo)
  1979. }
  1980. #endif
  1981. let request = VerifyAssertionRequest(providerID: credential.provider,
  1982. requestConfiguration: requestConfiguration)
  1983. request.autoCreate = !isReauthentication
  1984. credential.prepare(request)
  1985. let response = try await backend.call(with: request)
  1986. if response.needConfirmation {
  1987. let email = response.email
  1988. let credential = OAuthCredential(withVerifyAssertionResponse: response)
  1989. throw AuthErrorUtils.accountExistsWithDifferentCredentialError(
  1990. email: email,
  1991. updatedCredential: credential
  1992. )
  1993. }
  1994. guard let providerID = response.providerID, providerID.count > 0 else {
  1995. throw AuthErrorUtils.unexpectedResponse(deserializedResponse: response)
  1996. }
  1997. let user = try await completeSignIn(withAccessToken: response.idToken,
  1998. accessTokenExpirationDate: response
  1999. .approximateExpirationDate,
  2000. refreshToken: response.refreshToken,
  2001. anonymous: false)
  2002. let additionalUserInfo = AdditionalUserInfo(providerID: providerID,
  2003. profile: response.profile,
  2004. username: response.username,
  2005. isNewUser: response.isNewUser)
  2006. let updatedOAuthCredential = OAuthCredential(withVerifyAssertionResponse: response)
  2007. return AuthDataResult(withUser: user,
  2008. additionalUserInfo: additionalUserInfo,
  2009. credential: updatedOAuthCredential)
  2010. }
  2011. #if os(iOS)
  2012. /// Signs in using a phone credential.
  2013. /// - Parameter credential: The Phone Auth credential used to sign in.
  2014. /// - Parameter operation: The type of operation for which this sign-in attempt is initiated.
  2015. private func signIn(withPhoneCredential credential: PhoneAuthCredential,
  2016. operation: AuthOperationType) async throws -> VerifyPhoneNumberResponse {
  2017. switch credential.credentialKind {
  2018. case let .phoneNumber(phoneNumber, temporaryProof):
  2019. let request = VerifyPhoneNumberRequest(temporaryProof: temporaryProof,
  2020. phoneNumber: phoneNumber,
  2021. operation: operation,
  2022. requestConfiguration: requestConfiguration)
  2023. return try await backend.call(with: request)
  2024. case let .verification(verificationID, code):
  2025. guard verificationID.count > 0 else {
  2026. throw AuthErrorUtils.missingVerificationIDError(message: nil)
  2027. }
  2028. guard code.count > 0 else {
  2029. throw AuthErrorUtils.missingVerificationCodeError(message: nil)
  2030. }
  2031. let request = VerifyPhoneNumberRequest(verificationID: verificationID,
  2032. verificationCode: code,
  2033. operation: operation,
  2034. requestConfiguration: requestConfiguration)
  2035. return try await backend.call(with: request)
  2036. }
  2037. }
  2038. #endif
  2039. #if !os(watchOS)
  2040. /// Signs in using a game center credential.
  2041. /// - Parameter credential: The Game Center Auth Credential used to sign in.
  2042. private func signInAndRetrieveData(withGameCenterCredential credential: GameCenterAuthCredential) async throws
  2043. -> AuthDataResult {
  2044. guard let publicKeyURL = credential.publicKeyURL,
  2045. let signature = credential.signature,
  2046. let salt = credential.salt else {
  2047. fatalError(
  2048. "Internal Auth Error: Game Center credential missing publicKeyURL, signature, or salt"
  2049. )
  2050. }
  2051. let request = SignInWithGameCenterRequest(playerID: credential.playerID,
  2052. teamPlayerID: credential.teamPlayerID,
  2053. gamePlayerID: credential.gamePlayerID,
  2054. publicKeyURL: publicKeyURL,
  2055. signature: signature,
  2056. salt: salt,
  2057. timestamp: credential.timestamp,
  2058. displayName: credential.displayName,
  2059. requestConfiguration: requestConfiguration)
  2060. let response = try await backend.call(with: request)
  2061. let user = try await completeSignIn(withAccessToken: response.idToken,
  2062. accessTokenExpirationDate: response
  2063. .approximateExpirationDate,
  2064. refreshToken: response.refreshToken,
  2065. anonymous: false)
  2066. let additionalUserInfo = AdditionalUserInfo(providerID: GameCenterAuthProvider.id,
  2067. profile: nil,
  2068. username: nil,
  2069. isNewUser: response.isNewUser)
  2070. return AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo)
  2071. }
  2072. #endif
  2073. /// Signs in using an email and email sign-in link.
  2074. /// - Parameter email: The user's email address.
  2075. /// - Parameter link: The email sign-in link.
  2076. private func internalSignInAndRetrieveData(withEmail email: String,
  2077. link: String) async throws -> AuthDataResult {
  2078. guard isSignIn(withEmailLink: link) else {
  2079. fatalError("The link provided is not valid for email/link sign-in. Please check the link by " +
  2080. "calling isSignIn(withEmailLink:) on the Auth instance before attempting to use it " +
  2081. "for email/link sign-in.")
  2082. }
  2083. let queryItems = getQueryItems(link)
  2084. guard let actionCode = queryItems["oobCode"] else {
  2085. fatalError("Missing oobCode in link URL")
  2086. }
  2087. let request = EmailLinkSignInRequest(email: email,
  2088. oobCode: actionCode,
  2089. requestConfiguration: requestConfiguration)
  2090. let response = try await backend.call(with: request)
  2091. let user = try await completeSignIn(withAccessToken: response.idToken,
  2092. accessTokenExpirationDate: response
  2093. .approximateExpirationDate,
  2094. refreshToken: response.refreshToken,
  2095. anonymous: false)
  2096. let additionalUserInfo = AdditionalUserInfo(providerID: EmailAuthProvider.id,
  2097. profile: nil,
  2098. username: nil,
  2099. isNewUser: response.isNewUser)
  2100. return AuthDataResult(withUser: user, additionalUserInfo: additionalUserInfo)
  2101. }
  2102. private func getQueryItems(_ link: String) -> [String: String] {
  2103. var queryItems = AuthWebUtils.parseURL(link)
  2104. if queryItems.count == 0 {
  2105. let urlComponents = URLComponents(string: link)
  2106. if let query = urlComponents?.query {
  2107. queryItems = AuthWebUtils.parseURL(query)
  2108. }
  2109. }
  2110. return queryItems
  2111. }
  2112. /// Creates a AuthDataResultCallback block which wraps another AuthDataResultCallback;
  2113. /// trying to update the current user before forwarding it's invocations along to a subject
  2114. /// block.
  2115. ///
  2116. /// Typically invoked as part of the complete sign-in flow. For any other uses please
  2117. /// consider alternative ways of updating the current user.
  2118. /// - Parameter callback: Called when the user has been updated or when an error has occurred.
  2119. /// Invoked asynchronously on the main thread in the future.
  2120. /// - Returns: Returns a block that updates the current user.
  2121. func signInFlowAuthDataResultCallback(byDecorating callback:
  2122. ((AuthDataResult?, Error?) -> Void)?) -> (Result<AuthDataResult, Error>) -> Void {
  2123. return { result in
  2124. switch result {
  2125. case let .success(authResult):
  2126. do {
  2127. try self.updateCurrentUser(authResult.user, byForce: false, savingToDisk: true)
  2128. Auth.wrapMainAsync(callback: callback, with: .success(authResult))
  2129. } catch {
  2130. Auth.wrapMainAsync(callback: callback, with: .failure(error))
  2131. }
  2132. case let .failure(error):
  2133. Auth.wrapMainAsync(callback: callback, with: .failure(error))
  2134. }
  2135. }
  2136. }
  2137. private func wrapAsyncRPCTask(_ request: any AuthRPCRequest, _ callback: ((Error?) -> Void)?) {
  2138. Task {
  2139. do {
  2140. let _ = try await self.backend.call(with: request)
  2141. Auth.wrapMainAsync(callback, nil)
  2142. } catch {
  2143. Auth.wrapMainAsync(callback, error)
  2144. }
  2145. }
  2146. }
  2147. class func wrapMainAsync(_ callback: ((Error?) -> Void)?, _ error: Error?) {
  2148. if let callback {
  2149. DispatchQueue.main.async {
  2150. callback(error)
  2151. }
  2152. }
  2153. }
  2154. class func wrapMainAsync<T: Any>(callback: ((T?, Error?) -> Void)?,
  2155. with result: Result<T, Error>) -> Void {
  2156. guard let callback else { return }
  2157. DispatchQueue.main.async {
  2158. switch result {
  2159. case let .success(success): callback(success, nil)
  2160. case let .failure(error): callback(nil, error)
  2161. }
  2162. }
  2163. }
  2164. #if os(iOS)
  2165. private func wrapInjectRecaptcha<T: AuthRPCRequest>(request: T,
  2166. action: AuthRecaptchaAction,
  2167. _ callback: @escaping (
  2168. (T.Response?, Error?) -> Void
  2169. )) {
  2170. Task {
  2171. do {
  2172. let response = try await injectRecaptcha(request: request, action: action)
  2173. callback(response, nil)
  2174. } catch {
  2175. callback(nil, error)
  2176. }
  2177. }
  2178. }
  2179. func injectRecaptcha<T: AuthRPCRequest>(request: T,
  2180. action: AuthRecaptchaAction) async throws -> T
  2181. .Response {
  2182. let recaptchaVerifier = AuthRecaptchaVerifier.shared(auth: self)
  2183. if recaptchaVerifier.enablementStatus(forProvider: AuthRecaptchaProvider.password) != .off {
  2184. try await recaptchaVerifier.injectRecaptchaFields(request: request,
  2185. provider: AuthRecaptchaProvider.password,
  2186. action: action)
  2187. } else {
  2188. do {
  2189. return try await backend.call(with: request)
  2190. } catch {
  2191. let nsError = error as NSError
  2192. if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError,
  2193. nsError.code == AuthErrorCode.internalError.rawValue,
  2194. let messages = underlyingError
  2195. .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable],
  2196. let message = messages["message"] as? String,
  2197. message.hasPrefix("MISSING_RECAPTCHA_TOKEN") {
  2198. try await recaptchaVerifier.injectRecaptchaFields(
  2199. request: request,
  2200. provider: AuthRecaptchaProvider.password,
  2201. action: action
  2202. )
  2203. } else {
  2204. throw error
  2205. }
  2206. }
  2207. }
  2208. return try await backend.call(with: request)
  2209. }
  2210. #endif
  2211. // MARK: Internal properties
  2212. /// Allow tests to swap in an alternate mainBundle, including ObjC unit tests via CocoaPods.
  2213. #if FIREBASE_CI
  2214. @objc public var mainBundleUrlTypes: [[String: Any]]!
  2215. #else
  2216. var mainBundleUrlTypes: [[String: Any]]!
  2217. #endif
  2218. /// The configuration object comprising of parameters needed to make a request to Firebase
  2219. /// Auth's backend.
  2220. var requestConfiguration: AuthRequestConfiguration
  2221. let backend: AuthBackend
  2222. #if os(iOS)
  2223. /// The manager for APNs tokens used by phone number auth.
  2224. var tokenManager: AuthAPNSTokenManager!
  2225. /// The manager for app credentials used by phone number auth.
  2226. var appCredentialManager: AuthAppCredentialManager!
  2227. /// The manager for remote notifications used by phone number auth.
  2228. var notificationManager: AuthNotificationManager!
  2229. /// An object that takes care of presenting URLs via the auth instance.
  2230. var authURLPresenter: AuthWebViewControllerDelegate
  2231. #endif // TARGET_OS_IOS
  2232. // MARK: Private properties
  2233. /// The stored user manager.
  2234. private let storedUserManager: AuthStoredUserManager
  2235. /// The Firebase app name.
  2236. private let firebaseAppName: String
  2237. private let authDispatcher: AuthDispatcher
  2238. /// The keychain service.
  2239. private let keychainServices: AuthKeychainServices
  2240. /// The user access (ID) token used last time for posting auth state changed notification.
  2241. private var lastNotifiedUserToken: String?
  2242. /// This flag denotes whether or not tokens should be automatically refreshed.
  2243. /// Will only be set to `true` if the another Firebase service is included (additionally to
  2244. /// Firebase Auth).
  2245. private var autoRefreshTokens = false
  2246. /// Whether or not token auto-refresh is currently scheduled.
  2247. private var autoRefreshScheduled = false
  2248. /// A flag that is set to YES if the app is put in the background and no when the app is
  2249. /// returned to the foreground.
  2250. private var isAppInBackground = false
  2251. /// An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification.
  2252. private var applicationDidBecomeActiveObserver: NSObjectProtocol?
  2253. /// An opaque object to act as the observer for
  2254. /// UIApplicationDidEnterBackgroundNotification.
  2255. private var applicationDidEnterBackgroundObserver: NSObjectProtocol?
  2256. /// An opaque object to act as the observer for
  2257. /// UIApplicationProtectedDataDidBecomeAvailable.
  2258. private var protectedDataDidBecomeAvailableObserver: NSObjectProtocol?
  2259. /// Key of user stored in the keychain. Prefixed with a Firebase app name.
  2260. private let kUserKey = "_firebase_user"
  2261. /// Handles returned from `NSNotificationCenter` for blocks which are "auth state did
  2262. /// change" notification listeners.
  2263. ///
  2264. /// Mutations should occur within a @synchronized(self) context.
  2265. private var listenerHandles: NSMutableArray = []
  2266. }