Auth.swift 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884
  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. tokenManagerGet().cancel(withError: error)
  50. }
  51. open func application(_ application: UIApplication,
  52. didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  53. fetchCompletionHandler completionHandler:
  54. @escaping (UIBackgroundFetchResult) -> Void) {
  55. _ = canHandleNotification(userInfo)
  56. completionHandler(UIBackgroundFetchResult.noData)
  57. }
  58. open func application(_ application: UIApplication,
  59. open url: URL,
  60. options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
  61. return canHandle(url)
  62. }
  63. }
  64. #endif
  65. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  66. extension Auth: AuthInterop {
  67. func getTokenInternal(forcingRefresh forceRefresh: Bool) {
  68. // Enable token auto-refresh if not already enabled.
  69. if !autoRefreshTokens {
  70. AuthLog.logInfo(code: "I-AUT000002", message: "Token auto-refresh enabled.")
  71. autoRefreshTokens = true
  72. scheduleAutoTokenRefresh()
  73. #if canImport(UIKit) // Is a similar mechanism needed on macOS?
  74. applicationDidBecomeActiveObserver =
  75. NotificationCenter.default.addObserver(
  76. forName: UIApplication.didBecomeActiveNotification,
  77. object: nil, queue: nil
  78. ) { notification in
  79. self.isAppInBackground = false
  80. if !self.autoRefreshScheduled {
  81. self.scheduleAutoTokenRefresh()
  82. }
  83. }
  84. applicationDidEnterBackgroundObserver =
  85. NotificationCenter.default.addObserver(
  86. forName: UIApplication.didEnterBackgroundNotification,
  87. object: nil, queue: nil
  88. ) { notification in
  89. self.isAppInBackground = true
  90. }
  91. #endif
  92. }
  93. }
  94. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  95. /// TODO: Switch protocol and implementation to Swift when clients are all Swift.
  96. ///
  97. /// This method is not for public use. It is for Firebase clients of AuthInterop.
  98. @objc(getTokenForcingRefresh:withCallback:)
  99. public func getToken(forcingRefresh forceRefresh: Bool,
  100. completion: @escaping (String?, Error?) -> Void) {
  101. Task {
  102. do {
  103. let token = try await authWorker.getToken(forcingRefresh: forceRefresh)
  104. await MainActor.run {
  105. completion(token, nil)
  106. }
  107. } catch {
  108. await MainActor.run {
  109. completion(nil, error)
  110. }
  111. }
  112. }
  113. }
  114. /// Get the current Auth user's UID. Returns nil if there is no user signed in.
  115. ///
  116. /// This method is not for public use. It is for Firebase clients of AuthInterop.
  117. open func getUserID() -> String? {
  118. return currentUser?.uid
  119. }
  120. }
  121. /// Manages authentication for Firebase apps.
  122. ///
  123. /// This class is thread-safe.
  124. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  125. @objc(FIRAuth) open class Auth: NSObject {
  126. /// Gets the auth object for the default Firebase app.
  127. ///
  128. /// The default Firebase app must have already been configured or an exception will be raised.
  129. @objc open class func auth() -> Auth {
  130. guard let defaultApp = FirebaseApp.app() else {
  131. fatalError("The default FirebaseApp instance must be configured before the default Auth " +
  132. "instance can be initialized. One way to ensure this is to call " +
  133. "`FirebaseApp.configure()` in the App Delegate's " +
  134. "`application(_:didFinishLaunchingWithOptions:)` (or the `@main` struct's " +
  135. "initializer in SwiftUI).")
  136. }
  137. return auth(app: defaultApp)
  138. }
  139. /// Gets the auth object for a `FirebaseApp`.
  140. /// - Parameter app: The app for which to retrieve the associated `Auth` instance.
  141. /// - Returns: The `Auth` instance associated with the given app.
  142. @objc open class func auth(app: FirebaseApp) -> Auth {
  143. return ComponentType<AuthInterop>.instance(for: AuthInterop.self, in: app.container) as! Auth
  144. }
  145. /// Gets the `FirebaseApp` object that this auth object is connected to.
  146. @objc public internal(set) weak var app: FirebaseApp?
  147. /// Synchronously gets the cached current user, or null if there is none.
  148. @objc public internal(set) var currentUser: User?
  149. /// The current user language code.
  150. ///
  151. /// This property can be set to the app's current language by
  152. /// calling `useAppLanguage()`.
  153. ///
  154. /// The string used to set this property must be a language code that follows BCP 47.
  155. @objc open var languageCode: String? {
  156. get {
  157. getLanguageCode()
  158. }
  159. set(val) {
  160. setLanguageCode(val)
  161. }
  162. }
  163. /// Contains settings related to the auth object.
  164. @NSCopying @objc open var settings: AuthSettings?
  165. /// The current user access group that the Auth instance is using.
  166. ///
  167. /// Default is `nil`.
  168. @objc public internal(set) var userAccessGroup: String?
  169. /// Contains shareAuthStateAcrossDevices setting related to the auth object.
  170. ///
  171. /// If userAccessGroup is not set, setting shareAuthStateAcrossDevices will
  172. /// have no effect. You should set shareAuthStateAcrossDevices to its desired
  173. /// state and then set the userAccessGroup after.
  174. @objc open var shareAuthStateAcrossDevices: Bool = false
  175. /// The tenant ID of the auth instance. `nil` if none is available.
  176. @objc open var tenantID: String?
  177. /// The custom authentication domain used to handle all sign-in redirects.
  178. /// End-users will see
  179. /// this domain when signing in. This domain must be allowlisted in the Firebase Console.
  180. @objc open var customAuthDomain: String?
  181. /// [Deprecated] Fetches the list of all sign-in methods previously used for the provided
  182. /// email address. This method returns an empty list when [Email Enumeration
  183. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  184. /// is enabled, irrespective of the number of authentication methods available for the given
  185. /// email.
  186. ///
  187. /// Possible error codes: `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  188. ///
  189. /// - Parameter email: The email address for which to obtain a list of sign-in methods.
  190. /// - Parameter completion: Optionally; a block which is invoked when the list of sign in methods
  191. /// for the specified email address is ready or an error was encountered. Invoked asynchronously
  192. /// on the main thread in the future.
  193. #if !FIREBASE_CI
  194. @available(
  195. *,
  196. deprecated,
  197. message: "`fetchSignInMethods` is deprecated and will be removed in a future release. This method returns an empty list when Email Enumeration Protection is enabled."
  198. )
  199. #endif // !FIREBASE_CI
  200. @objc open func fetchSignInMethods(forEmail email: String,
  201. completion: (([String]?, Error?) -> Void)? = nil) {
  202. Task {
  203. do {
  204. let result = try await fetchSignInMethods(forEmail: email)
  205. await MainActor.run {
  206. completion?(result, nil)
  207. }
  208. } catch {
  209. await MainActor.run {
  210. completion?(nil, error)
  211. }
  212. }
  213. }
  214. }
  215. /// [Deprecated] Fetches the list of all sign-in methods previously used for the provided
  216. /// email address. This method returns an empty list when [Email Enumeration
  217. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  218. /// is enabled, irrespective of the number of authentication methods available for the given
  219. /// email.
  220. ///
  221. /// Possible error codes: `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  222. ///
  223. /// - Parameter email: The email address for which to obtain a list of sign-in methods.
  224. /// - Returns: List of sign-in methods
  225. @available(
  226. *,
  227. deprecated,
  228. message: "`fetchSignInMethods` is deprecated and will be removed in a future release. This method returns an empty list when Email Enumeration Protection is enabled."
  229. )
  230. open func fetchSignInMethods(forEmail email: String) async throws -> [String] {
  231. return try await authWorker.fetchSignInMethods(forEmail: email)
  232. }
  233. /// Signs in using an email address and password.
  234. ///
  235. /// When [Email Enumeration
  236. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  237. /// is enabled, this method fails with an error in case of an invalid
  238. /// email/password.
  239. ///
  240. /// Possible error codes:
  241. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  242. /// accounts are not enabled. Enable them in the Auth section of the
  243. /// Firebase console.
  244. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  245. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  246. /// sign in with an incorrect password.
  247. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  248. /// - Parameter email: The user's email address.
  249. /// - Parameter password: The user's password.
  250. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  251. /// or is canceled. Invoked asynchronously on the main thread in the future.
  252. @objc open func signIn(withEmail email: String,
  253. password: String,
  254. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  255. Task {
  256. do {
  257. let result = try await signIn(withEmail: email, password: password)
  258. await MainActor.run {
  259. completion?(result, nil)
  260. }
  261. } catch {
  262. await MainActor.run {
  263. completion?(nil, error)
  264. }
  265. }
  266. }
  267. }
  268. /// Signs in using an email address and password.
  269. ///
  270. /// Possible error codes:
  271. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  272. /// accounts are not enabled. Enable them in the Auth section of the
  273. /// Firebase console.
  274. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  275. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  276. /// sign in with an incorrect password.
  277. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  278. /// - Parameter email: The user's email address.
  279. /// - Parameter password: The user's password.
  280. /// - Returns: The `AuthDataResult` after a successful signin.
  281. @discardableResult
  282. open func signIn(withEmail email: String, password: String) async throws -> AuthDataResult {
  283. let result = try await authWorker.signIn(withEmail: email, password: password)
  284. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  285. return result
  286. }
  287. /// Signs in using an email address and email sign-in link.
  288. ///
  289. /// Possible error codes:
  290. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  291. /// accounts are not enabled. Enable them in the Auth section of the
  292. /// Firebase console.
  293. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  294. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  295. /// sign in with an incorrect password.
  296. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  297. /// - Parameter email: The user's email address.
  298. /// - Parameter link: The email sign-in link.
  299. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  300. /// or is canceled. Invoked asynchronously on the main thread in the future.
  301. @objc open func signIn(withEmail email: String,
  302. link: String,
  303. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  304. Task {
  305. do {
  306. let result = try await signIn(withEmail: email, link: link)
  307. await MainActor.run {
  308. completion?(result, nil)
  309. }
  310. } catch {
  311. await MainActor.run {
  312. completion?(nil, error)
  313. }
  314. }
  315. }
  316. }
  317. /// Signs in using an email address and email sign-in link.
  318. /// Possible error codes:
  319. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  320. /// accounts are not enabled. Enable them in the Auth section of the
  321. /// Firebase console.
  322. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  323. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  324. /// sign in with an incorrect password.
  325. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  326. /// - Parameter email: The user's email address.
  327. /// - Parameter link: The email sign-in link.
  328. /// - Returns: The `AuthDataResult` after a successful signin.
  329. open func signIn(withEmail email: String, link: String) async throws -> AuthDataResult {
  330. let result = try await authWorker.signIn(withEmail: email, link: link)
  331. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  332. return result
  333. }
  334. #if os(iOS)
  335. /// Signs in using the provided auth provider instance.
  336. ///
  337. /// Possible error codes:
  338. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  339. /// accounts are not enabled. Enable them in the Auth section of the
  340. /// Firebase console.
  341. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  342. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  343. /// sign in with an incorrect password.
  344. /// * `AuthErrorCodeWebNetworkRequestFailed` - Indicates that a network request within a
  345. /// SFSafariViewController or WKWebView failed.
  346. /// * `AuthErrorCodeWebInternalError` - Indicates that an internal error occurred within a
  347. /// SFSafariViewController or WKWebView.
  348. /// * `AuthErrorCodeWebSignInUserInteractionFailure` - Indicates a general failure during
  349. /// a web sign-in flow.
  350. /// * `AuthErrorCodeWebContextAlreadyPresented` - Indicates that an attempt was made to
  351. /// present a new web context while one was already being presented.
  352. /// * `AuthErrorCodeWebContextCancelled` - Indicates that the URL presentation was
  353. /// cancelled prematurely by the user.
  354. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  355. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  356. /// existing account, that cannot be authenticated with this sign-in method. Call
  357. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  358. /// the sign-in providers returned. This error will only be thrown if the "One account per
  359. /// email address" setting is enabled in the Firebase console, under Auth settings.
  360. /// - Parameter provider: An instance of an auth provider used to initiate the sign-in flow.
  361. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the AuthUIDelegate
  362. /// protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate
  363. /// will be used.
  364. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  365. /// or is canceled. Invoked asynchronously on the main thread in the future.
  366. @available(tvOS, unavailable)
  367. @available(macOS, unavailable)
  368. @available(watchOS, unavailable)
  369. @objc(signInWithProvider:UIDelegate:completion:)
  370. open func signIn(with provider: FederatedAuthProvider,
  371. uiDelegate: AuthUIDelegate?,
  372. completion: ((AuthDataResult?, Error?) -> Void)?) {
  373. Task {
  374. do {
  375. let result = try await signIn(with: provider, uiDelegate: uiDelegate)
  376. await MainActor.run {
  377. completion?(result, nil)
  378. }
  379. } catch {
  380. await MainActor.run {
  381. completion?(nil, error)
  382. }
  383. }
  384. }
  385. }
  386. /// Signs in using the provided auth provider instance.
  387. ///
  388. /// Possible error codes:
  389. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password
  390. /// accounts are not enabled. Enable them in the Auth section of the
  391. /// Firebase console.
  392. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  393. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted
  394. /// sign in with an incorrect password.
  395. /// * `AuthErrorCodeWebNetworkRequestFailed` - Indicates that a network request within a
  396. /// SFSafariViewController or WKWebView failed.
  397. /// * `AuthErrorCodeWebInternalError` - Indicates that an internal error occurred within a
  398. /// SFSafariViewController or WKWebView.
  399. /// * `AuthErrorCodeWebSignInUserInteractionFailure` - Indicates a general failure during
  400. /// a web sign-in flow.
  401. /// * `AuthErrorCodeWebContextAlreadyPresented` - Indicates that an attempt was made to
  402. /// present a new web context while one was already being presented.
  403. /// * `AuthErrorCodeWebContextCancelled` - Indicates that the URL presentation was
  404. /// cancelled prematurely by the user.
  405. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  406. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  407. /// existing account, that cannot be authenticated with this sign-in method. Call
  408. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  409. /// the sign-in providers returned. This error will only be thrown if the "One account per
  410. /// email address" setting is enabled in the Firebase console, under Auth settings.
  411. /// - Parameter provider: An instance of an auth provider used to initiate the sign-in flow.
  412. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the AuthUIDelegate
  413. /// protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate
  414. /// will be used.
  415. /// - Returns: The `AuthDataResult` after the successful signin.
  416. @available(tvOS, unavailable)
  417. @available(macOS, unavailable)
  418. @available(watchOS, unavailable)
  419. @discardableResult
  420. open func signIn(with provider: FederatedAuthProvider,
  421. uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
  422. let result = try await authWorker.signIn(with: provider, uiDelegate: uiDelegate)
  423. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  424. return result
  425. }
  426. #endif // iOS
  427. /// Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook
  428. /// login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional
  429. /// identity provider data.
  430. ///
  431. /// Possible error codes:
  432. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  433. /// This could happen if it has expired or it is malformed.
  434. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts
  435. /// with the identity provider represented by the credential are not enabled.
  436. /// Enable them in the Auth section of the Firebase console.
  437. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  438. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  439. /// existing account, that cannot be authenticated with this sign-in method. Call
  440. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  441. /// the sign-in providers returned. This error will only be thrown if the "One account per
  442. /// email address" setting is enabled in the Firebase console, under Auth settings.
  443. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  444. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an
  445. /// incorrect password, if credential is of the type EmailPasswordAuthCredential.
  446. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  447. /// * `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was
  448. /// created with an empty verification ID.
  449. /// * `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential
  450. /// was created with an empty verification code.
  451. /// * `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential
  452. /// was created with an invalid verification Code.
  453. /// * `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was
  454. /// created with an invalid verification ID.
  455. /// * `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired.
  456. /// - Parameter credential: The credential supplied by the IdP.
  457. /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes,
  458. /// or is canceled. Invoked asynchronously on the main thread in the future.
  459. @objc(signInWithCredential:completion:)
  460. open func signIn(with credential: AuthCredential,
  461. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  462. Task {
  463. do {
  464. let result = try await signIn(with: credential)
  465. await MainActor.run {
  466. completion?(result, nil)
  467. }
  468. } catch {
  469. await MainActor.run {
  470. completion?(nil, error)
  471. }
  472. }
  473. }
  474. }
  475. /// Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook
  476. /// login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional
  477. /// identity provider data.
  478. ///
  479. /// Possible error codes:
  480. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  481. /// This could happen if it has expired or it is malformed.
  482. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts
  483. /// with the identity provider represented by the credential are not enabled.
  484. /// Enable them in the Auth section of the Firebase console.
  485. /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted
  486. /// by the credential (e.g. the email in a Facebook access token) is already in use by an
  487. /// existing account, that cannot be authenticated with this sign-in method. Call
  488. /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of
  489. /// the sign-in providers returned. This error will only be thrown if the "One account per
  490. /// email address" setting is enabled in the Firebase console, under Auth settings.
  491. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  492. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an
  493. /// incorrect password, if credential is of the type EmailPasswordAuthCredential.
  494. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  495. /// * `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was
  496. /// created with an empty verification ID.
  497. /// * `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential
  498. /// was created with an empty verification code.
  499. /// * `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential
  500. /// was created with an invalid verification Code.
  501. /// * `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was
  502. /// created with an invalid verification ID.
  503. /// * `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired.
  504. /// - Parameter credential: The credential supplied by the IdP.
  505. /// - Returns: The `AuthDataResult` after the successful signin.
  506. @discardableResult
  507. open func signIn(with credential: AuthCredential) async throws -> AuthDataResult {
  508. let result = try await authWorker.signIn(with: credential)
  509. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  510. return result
  511. }
  512. /// Asynchronously creates and becomes an anonymous user.
  513. ///
  514. /// If there is already an anonymous user signed in, that user will be returned instead.
  515. /// If there is any other existing user signed in, that user will be signed out.
  516. ///
  517. /// Possible error codes:
  518. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are
  519. /// not enabled. Enable them in the Auth section of the Firebase console.
  520. /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is
  521. /// canceled. Invoked asynchronously on the main thread in the future.
  522. @objc open func signInAnonymously(completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  523. Task {
  524. do {
  525. let result = try await signInAnonymously()
  526. await MainActor.run {
  527. completion?(result, nil)
  528. }
  529. } catch {
  530. await MainActor.run {
  531. completion?(nil, error)
  532. }
  533. }
  534. }
  535. }
  536. /// Asynchronously creates and becomes an anonymous user.
  537. ///
  538. /// If there is already an anonymous user signed in, that user will be returned instead.
  539. /// If there is any other existing user signed in, that user will be signed out.
  540. ///
  541. /// Possible error codes:
  542. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are
  543. /// not enabled. Enable them in the Auth section of the Firebase console.
  544. /// - Returns: The `AuthDataResult` after the successful signin.
  545. @discardableResult
  546. @objc open func signInAnonymously() async throws -> AuthDataResult {
  547. let result = try await authWorker.signInAnonymously()
  548. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  549. return result
  550. }
  551. /// Asynchronously signs in to Firebase with the given Auth token.
  552. ///
  553. /// Possible error codes:
  554. /// * `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with
  555. /// the custom token.
  556. /// * `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key
  557. /// belong to different projects.
  558. /// - Parameter token: A self-signed custom auth token.
  559. /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is
  560. /// canceled. Invoked asynchronously on the main thread in the future.
  561. @objc open func signIn(withCustomToken token: String,
  562. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  563. Task {
  564. do {
  565. let result = try await signIn(withCustomToken: token)
  566. await MainActor.run {
  567. completion?(result, nil)
  568. }
  569. } catch {
  570. await MainActor.run {
  571. completion?(nil, error)
  572. }
  573. }
  574. }
  575. }
  576. /// Asynchronously signs in to Firebase with the given Auth token.
  577. ///
  578. /// Possible error codes:
  579. /// * `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with
  580. /// the custom token.
  581. /// * `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key
  582. /// belong to different projects.
  583. /// - Parameter token: A self-signed custom auth token.
  584. /// - Returns: The `AuthDataResult` after the successful signin.
  585. @discardableResult
  586. open func signIn(withCustomToken token: String) async throws -> AuthDataResult {
  587. let result = try await authWorker.signIn(withCustomToken: token)
  588. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  589. return result
  590. }
  591. /// Creates and, on success, signs in a user with the given email address and password.
  592. ///
  593. /// Possible error codes:
  594. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  595. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up
  596. /// already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user
  597. /// used, and prompt the user to sign in with one of those.
  598. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts
  599. /// are not enabled. Enable them in the Auth section of the Firebase console.
  600. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  601. /// considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo
  602. /// dictionary object will contain more detailed explanation that can be shown to the user.
  603. /// - Parameter email: The user's email address.
  604. /// - Parameter password: The user's desired password.
  605. /// - Parameter completion: Optionally; a block which is invoked when the sign up flow finishes,
  606. /// or is canceled. Invoked asynchronously on the main thread in the future.
  607. @objc open func createUser(withEmail email: String,
  608. password: String,
  609. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  610. Task {
  611. do {
  612. let result = try await createUser(withEmail: email, password: password)
  613. await MainActor.run {
  614. completion?(result, nil)
  615. }
  616. } catch {
  617. await MainActor.run {
  618. completion?(nil, error)
  619. }
  620. }
  621. }
  622. }
  623. /// Creates and, on success, signs in a user with the given email address and password.
  624. ///
  625. /// Possible error codes:
  626. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  627. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up
  628. /// already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user
  629. /// used, and prompt the user to sign in with one of those.
  630. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts
  631. /// are not enabled. Enable them in the Auth section of the Firebase console.
  632. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  633. /// considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo
  634. /// dictionary object will contain more detailed explanation that can be shown to the user.
  635. /// - Parameter email: The user's email address.
  636. /// - Parameter password: The user's desired password.
  637. /// - Returns: The `AuthDataResult` after the successful signin.
  638. @discardableResult
  639. open func createUser(withEmail email: String, password: String) async throws -> AuthDataResult {
  640. guard password.count > 0 else {
  641. throw AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing password")
  642. }
  643. guard email.count > 0 else {
  644. throw AuthErrorUtils.missingEmailError(message: nil)
  645. }
  646. let result = try await authWorker.createUser(withEmail: email, password: password)
  647. try await authWorker.updateCurrentUser(result.user, byForce: false, savingToDisk: true)
  648. return result
  649. }
  650. /// Resets the password given a code sent to the user outside of the app and a new password
  651. /// for the user.
  652. ///
  653. /// Possible error codes:
  654. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  655. /// considered too weak.
  656. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign
  657. /// in with the specified identity provider.
  658. /// * `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired.
  659. /// * `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid.
  660. /// - Parameter code: The reset code.
  661. /// - Parameter newPassword: The new password.
  662. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  663. /// Invoked asynchronously on the main thread in the future.
  664. @objc open func confirmPasswordReset(withCode code: String, newPassword: String,
  665. completion: @escaping (Error?) -> Void) {
  666. Task {
  667. do {
  668. try await confirmPasswordReset(withCode: code, newPassword: newPassword)
  669. await MainActor.run {
  670. completion(nil)
  671. }
  672. } catch {
  673. await MainActor.run {
  674. completion(error)
  675. }
  676. }
  677. }
  678. }
  679. /// Resets the password given a code sent to the user outside of the app and a new password
  680. /// for the user.
  681. ///
  682. /// Possible error codes:
  683. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  684. /// considered too weak.
  685. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign
  686. /// in with the specified identity provider.
  687. /// * `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired.
  688. /// * `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid.
  689. /// - Parameter code: The reset code.
  690. /// - Parameter newPassword: The new password.
  691. open func confirmPasswordReset(withCode code: String, newPassword: String) async throws {
  692. try await authWorker.confirmPasswordReset(withCode: code, newPassword: newPassword)
  693. }
  694. /// Checks the validity of an out of band code.
  695. /// - Parameter code: The out of band code to check validity.
  696. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  697. /// Invoked
  698. /// asynchronously on the main thread in the future.
  699. @objc open func checkActionCode(_ code: String,
  700. completion: @escaping (ActionCodeInfo?, Error?) -> Void) {
  701. Task {
  702. do {
  703. let code = try await checkActionCode(code)
  704. await MainActor.run {
  705. completion(code, nil)
  706. }
  707. } catch {
  708. await MainActor.run {
  709. completion(nil, error)
  710. }
  711. }
  712. }
  713. }
  714. /// Checks the validity of an out of band code.
  715. /// - Parameter code: The out of band code to check validity.
  716. /// - Returns: An `ActionCodeInfo`.
  717. open func checkActionCode(_ code: String) async throws -> ActionCodeInfo {
  718. return try await authWorker.checkActionCode(code)
  719. }
  720. /// Checks the validity of a verify password reset code.
  721. /// - Parameter code: The password reset code to be verified.
  722. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  723. /// Invoked asynchronously on the main thread in the future.
  724. @objc open func verifyPasswordResetCode(_ code: String,
  725. completion: @escaping (String?, Error?) -> Void) {
  726. Task {
  727. do {
  728. let email = try await verifyPasswordResetCode(code)
  729. await MainActor.run {
  730. completion(email, nil)
  731. }
  732. } catch {
  733. await MainActor.run {
  734. completion(nil, error)
  735. }
  736. }
  737. }
  738. }
  739. /// Checks the validity of a verify password reset code.
  740. /// - Parameter code: The password reset code to be verified.
  741. /// - Returns: An email.
  742. open func verifyPasswordResetCode(_ code: String) async throws -> String {
  743. return try await authWorker.verifyPasswordResetCode(code)
  744. }
  745. /// Applies out of band code.
  746. ///
  747. /// This method will not work for out of band codes which require an additional parameter,
  748. /// such as password reset code.
  749. /// - Parameter code: The out of band code to be applied.
  750. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  751. /// Invoked asynchronously on the main thread in the future.
  752. @objc open func applyActionCode(_ code: String, completion: @escaping (Error?) -> Void) {
  753. Task {
  754. do {
  755. try await applyActionCode(code)
  756. await MainActor.run {
  757. completion(nil)
  758. }
  759. } catch {
  760. await MainActor.run {
  761. completion(error)
  762. }
  763. }
  764. }
  765. }
  766. /// Applies out of band code.
  767. ///
  768. /// This method will not work for out of band codes which require an additional parameter,
  769. /// such as password reset code.
  770. /// - Parameter code: The out of band code to be applied.
  771. open func applyActionCode(_ code: String) async throws {
  772. try await authWorker.applyActionCode(code)
  773. }
  774. /// Initiates a password reset for the given email address.
  775. ///
  776. /// This method does not throw an
  777. /// error when there's no user account with the given email address and [Email Enumeration
  778. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  779. /// is enabled.
  780. ///
  781. /// Possible error codes:
  782. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  783. /// sent in the request.
  784. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  785. /// the console for this action.
  786. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  787. /// sending update email.
  788. /// - Parameter email: The email address of the user.
  789. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  790. /// Invoked
  791. /// asynchronously on the main thread in the future.
  792. @objc open func sendPasswordReset(withEmail email: String,
  793. completion: ((Error?) -> Void)? = nil) {
  794. sendPasswordReset(withEmail: email, actionCodeSettings: nil, completion: completion)
  795. }
  796. /// Initiates a password reset for the given email address and `ActionCodeSettings` object.
  797. ///
  798. /// This method does not throw an
  799. /// error when there's no user account with the given email address and [Email Enumeration
  800. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  801. /// is enabled.
  802. ///
  803. /// Possible error codes:
  804. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  805. /// sent in the request.
  806. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  807. /// the console for this action.
  808. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  809. /// sending update email.
  810. /// * `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when
  811. /// `handleCodeInApp` is set to true.
  812. /// * `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name
  813. /// is missing when the `androidInstallApp` flag is set to true.
  814. /// * `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the
  815. /// continue URL is not allowlisted in the Firebase console.
  816. /// * `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the
  817. /// continue URL is not valid.
  818. /// - Parameter email: The email address of the user.
  819. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  820. /// handling action codes.
  821. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  822. /// Invoked asynchronously on the main thread in the future.
  823. @objc open func sendPasswordReset(withEmail email: String,
  824. actionCodeSettings: ActionCodeSettings?,
  825. completion: ((Error?) -> Void)? = nil) {
  826. Task {
  827. do {
  828. try await sendPasswordReset(withEmail: email, actionCodeSettings: actionCodeSettings)
  829. await MainActor.run {
  830. completion?(nil)
  831. }
  832. } catch {
  833. await MainActor.run {
  834. completion?(error)
  835. }
  836. }
  837. }
  838. }
  839. /// Initiates a password reset for the given email address and `ActionCodeSettings` object.
  840. ///
  841. /// This method does not throw an
  842. /// error when there's no user account with the given email address and [Email Enumeration
  843. /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  844. /// is enabled.
  845. ///
  846. /// Possible error codes:
  847. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  848. /// sent in the request.
  849. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  850. /// the console for this action.
  851. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  852. /// sending update email.
  853. /// * `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when
  854. /// `handleCodeInApp` is set to true.
  855. /// * `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name
  856. /// is missing when the `androidInstallApp` flag is set to true.
  857. /// * `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the
  858. /// continue URL is not allowlisted in the Firebase console.
  859. /// * `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the
  860. /// continue URL is not valid.
  861. /// - Parameter email: The email address of the user.
  862. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  863. /// handling action codes.
  864. open func sendPasswordReset(withEmail email: String,
  865. actionCodeSettings: ActionCodeSettings? = nil) async throws {
  866. try await authWorker.sendPasswordReset(withEmail: email, actionCodeSettings: actionCodeSettings)
  867. }
  868. /// Sends a sign in with email link to provided email address.
  869. /// - Parameter email: The email address of the user.
  870. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  871. /// handling action codes.
  872. /// - Parameter completion: Optionally; a block which is invoked when the request finishes.
  873. /// Invoked asynchronously on the main thread in the future.
  874. @objc open func sendSignInLink(toEmail email: String,
  875. actionCodeSettings: ActionCodeSettings,
  876. completion: ((Error?) -> Void)? = nil) {
  877. Task {
  878. do {
  879. try await sendSignInLink(toEmail: email, actionCodeSettings: actionCodeSettings)
  880. await MainActor.run {
  881. completion?(nil)
  882. }
  883. } catch {
  884. await MainActor.run {
  885. completion?(error)
  886. }
  887. }
  888. }
  889. }
  890. /// Sends a sign in with email link to provided email address.
  891. /// - Parameter email: The email address of the user.
  892. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  893. /// handling action codes.
  894. open func sendSignInLink(toEmail email: String,
  895. actionCodeSettings: ActionCodeSettings) async throws {
  896. if !actionCodeSettings.handleCodeInApp {
  897. fatalError("The handleCodeInApp flag in ActionCodeSettings must be true for Email-link " +
  898. "Authentication.")
  899. }
  900. try await authWorker.sendSignInLink(toEmail: email, actionCodeSettings: actionCodeSettings)
  901. }
  902. /// Signs out the current user.
  903. ///
  904. /// Possible error codes:
  905. /// * `AuthErrorCodeKeychainError` - Indicates an error occurred when accessing the
  906. /// keychain. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  907. /// dictionary will contain more information about the error encountered.
  908. @available(*, noasync, message: "Use the async version instead")
  909. @objc(signOut:) open func signOut() throws {
  910. let semaphore = DispatchSemaphore(value: 0)
  911. Task {
  912. try await authWorker.signOut()
  913. semaphore.signal()
  914. }
  915. semaphore.wait()
  916. }
  917. /// Signs out the current user.
  918. ///
  919. /// Possible error codes:
  920. /// * `AuthErrorCodeKeychainError` - Indicates an error occurred when accessing the
  921. /// keychain. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  922. /// dictionary will contain more information about the error encountered.
  923. open func signOut() async throws {
  924. try await authWorker.signOut()
  925. }
  926. /// Checks if link is an email sign-in link.
  927. /// - Parameter link: The email sign-in link.
  928. /// - Returns: `true` when the link passed matches the expected format of an email sign-in link.
  929. @objc open func isSignIn(withEmailLink link: String) -> Bool {
  930. guard !link.isEmpty else {
  931. return false
  932. }
  933. let queryItems = getQueryItems(link)
  934. if let _ = queryItems["oobCode"],
  935. let mode = queryItems["mode"],
  936. mode == "signIn" {
  937. return true
  938. }
  939. return false
  940. }
  941. #if os(iOS) && !targetEnvironment(macCatalyst)
  942. /// Initializes reCAPTCHA using the settings configured for the project or tenant.
  943. ///
  944. /// If you change the tenant ID of the `Auth` instance, the configuration will be
  945. /// reloaded.
  946. @objc(initializeRecaptchaConfigWithCompletion:)
  947. open func initializeRecaptchaConfig(completion: ((Error?) -> Void)?) {
  948. Task {
  949. do {
  950. try await initializeRecaptchaConfig()
  951. if let completion {
  952. completion(nil)
  953. }
  954. } catch {
  955. if let completion {
  956. completion(error)
  957. }
  958. }
  959. }
  960. }
  961. /// Initializes reCAPTCHA using the settings configured for the project or tenant.
  962. ///
  963. /// If you change the tenant ID of the `Auth` instance, the configuration will be
  964. /// reloaded.
  965. open func initializeRecaptchaConfig() async throws {
  966. // Trigger recaptcha verification flow to initialize the recaptcha client and
  967. // config. Recaptcha token will be returned.
  968. let verifier = AuthRecaptchaVerifier.shared(auth: self)
  969. _ = try await verifier.verify(forceRefresh: true, action: AuthRecaptchaAction.defaultAction)
  970. }
  971. #endif
  972. /// Registers a block as an "auth state did change" listener.
  973. ///
  974. /// To be invoked when:
  975. /// * The block is registered as a listener,
  976. /// * A user with a different UID from the current user has signed in, or
  977. /// * The current user has signed out.
  978. ///
  979. /// The block is invoked immediately after adding it according to its standard invocation
  980. /// semantics, asynchronously on the main thread. Users should pay special attention to
  981. /// making sure the block does not inadvertently retain objects which should not be retained by
  982. /// the long-lived block. The block itself will be retained by `Auth` until it is
  983. /// unregistered or until the `Auth` instance is otherwise deallocated.
  984. /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on
  985. /// the main thread, even for it's initial invocation after having been added as a listener.
  986. /// - Returns: A handle useful for manually unregistering the block as a listener.
  987. @objc(addAuthStateDidChangeListener:)
  988. open func addStateDidChangeListener(_ listener: @escaping (Auth, User?) -> Void)
  989. -> NSObjectProtocol {
  990. var firstInvocation = true
  991. var previousUserID: String?
  992. return addIDTokenDidChangeListener { auth, user in
  993. let shouldCallListener = firstInvocation || previousUserID != user?.uid
  994. firstInvocation = false
  995. previousUserID = user?.uid
  996. if shouldCallListener {
  997. listener(auth, user)
  998. }
  999. }
  1000. }
  1001. /// Unregisters a block as an "auth state did change" listener.
  1002. /// - Parameter listenerHandle: The handle for the listener.
  1003. @objc(removeAuthStateDidChangeListener:)
  1004. open func removeStateDidChangeListener(_ listenerHandle: NSObjectProtocol) {
  1005. NotificationCenter.default.removeObserver(listenerHandle)
  1006. objc_sync_enter(Auth.self)
  1007. defer { objc_sync_exit(Auth.self) }
  1008. listenerHandles.remove(listenerHandle)
  1009. }
  1010. /// Registers a block as an "ID token did change" listener.
  1011. ///
  1012. /// To be invoked when:
  1013. /// * The block is registered as a listener,
  1014. /// * A user with a different UID from the current user has signed in,
  1015. /// * The ID token of the current user has been refreshed, or
  1016. /// * The current user has signed out.
  1017. ///
  1018. /// The block is invoked immediately after adding it according to its standard invocation
  1019. /// semantics, asynchronously on the main thread. Users should pay special attention to
  1020. /// making sure the block does not inadvertently retain objects which should not be retained by
  1021. /// the long-lived block. The block itself will be retained by `Auth` until it is
  1022. /// unregistered or until the `Auth` instance is otherwise deallocated.
  1023. /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on
  1024. /// the main thread, even for it's initial invocation after having been added as a listener.
  1025. /// - Returns: A handle useful for manually unregistering the block as a listener.
  1026. @objc open func addIDTokenDidChangeListener(_ listener: @escaping (Auth, User?) -> Void)
  1027. -> NSObjectProtocol {
  1028. let handle = NotificationCenter.default.addObserver(
  1029. forName: Auth.authStateDidChangeNotification,
  1030. object: self,
  1031. queue: OperationQueue.main
  1032. ) { notification in
  1033. if let auth = notification.object as? Auth {
  1034. listener(auth, auth.currentUser)
  1035. }
  1036. }
  1037. objc_sync_enter(Auth.self)
  1038. listenerHandles.add(listener)
  1039. objc_sync_exit(Auth.self)
  1040. DispatchQueue.main.async {
  1041. listener(self, self.currentUser)
  1042. }
  1043. return handle
  1044. }
  1045. /// Unregisters a block as an "ID token did change" listener.
  1046. /// - Parameter listenerHandle: The handle for the listener.
  1047. @objc open func removeIDTokenDidChangeListener(_ listenerHandle: NSObjectProtocol) {
  1048. NotificationCenter.default.removeObserver(listenerHandle)
  1049. objc_sync_enter(Auth.self)
  1050. listenerHandles.remove(listenerHandle)
  1051. objc_sync_exit(Auth.self)
  1052. }
  1053. /// Sets `languageCode` to the app's current language.
  1054. @objc open func useAppLanguage() {
  1055. setLanguageCode(Locale.preferredLanguages.first)
  1056. }
  1057. /// Configures Firebase Auth to connect to an emulated host instead of the remote backend.
  1058. @objc open func useEmulator(withHost host: String, port: Int) {
  1059. let semaphore = DispatchSemaphore(value: 0)
  1060. Task {
  1061. await useEmulator(withHost: host, port: port)
  1062. semaphore.signal()
  1063. }
  1064. semaphore.wait()
  1065. }
  1066. open func useEmulator(withHost host: String, port: Int) async {
  1067. guard host.count > 0 else {
  1068. fatalError("Cannot connect to empty host")
  1069. }
  1070. await authWorker.useEmulator(withHost: host, port: port)
  1071. }
  1072. /// Revoke the users token with authorization code.
  1073. /// - Parameter completion: (Optional) the block invoked when the request to revoke the token is
  1074. /// complete, or fails. Invoked asynchronously on the main thread in the future.
  1075. @objc open func revokeToken(withAuthorizationCode authorizationCode: String,
  1076. completion: ((Error?) -> Void)? = nil) {
  1077. Task {
  1078. do {
  1079. try await revokeToken(withAuthorizationCode: authorizationCode)
  1080. if let completion {
  1081. completion(nil)
  1082. }
  1083. } catch {
  1084. if let completion {
  1085. completion(error)
  1086. }
  1087. }
  1088. }
  1089. }
  1090. /// Revoke the users token with authorization code.
  1091. /// - Parameter completion: (Optional) the block invoked when the request to revoke the token is
  1092. /// complete, or fails. Invoked asynchronously on the main thread in the future.
  1093. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  1094. open func revokeToken(withAuthorizationCode authorizationCode: String) async throws {
  1095. if let currentUser {
  1096. let idToken = try await currentUser.internalGetToken()
  1097. let request = RevokeTokenRequest(withToken: authorizationCode,
  1098. idToken: idToken,
  1099. requestConfiguration: requestConfiguration)
  1100. let _ = try await AuthBackend.call(with: request)
  1101. }
  1102. }
  1103. /// Switch userAccessGroup and current user to the given accessGroup and the user stored in it.
  1104. @objc open func useUserAccessGroup(_ accessGroup: String?) throws {
  1105. // self.storedUserManager is initialized asynchronously. Make sure it is done.
  1106. kAuthGlobalWorkQueue.sync {}
  1107. return try internalUseUserAccessGroup(accessGroup)
  1108. }
  1109. func internalUseUserAccessGroup(_ accessGroup: String?) throws {
  1110. storedUserManager.setStoredUserAccessGroup(accessGroup: accessGroup)
  1111. let user = try getStoredUser(forAccessGroup: accessGroup)
  1112. try updateCurrentUser(user, byForce: false, savingToDisk: false)
  1113. if userAccessGroup == nil, accessGroup != nil {
  1114. let userKey = "\(firebaseAppName)\(kUserKey)"
  1115. try keychainServices.removeData(forKey: userKey)
  1116. }
  1117. userAccessGroup = accessGroup
  1118. lastNotifiedUserToken = user?.rawAccessToken()
  1119. }
  1120. /// Get the stored user in the given accessGroup.
  1121. ///
  1122. /// This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`.
  1123. /// and will return `nil`.
  1124. /// Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details.
  1125. @available(swift 1000.0) // Objective-C only API
  1126. @objc(getStoredUserForAccessGroup:error:)
  1127. open func __getStoredUser(forAccessGroup accessGroup: String?,
  1128. error outError: NSErrorPointer) -> User? {
  1129. do {
  1130. return try getStoredUser(forAccessGroup: accessGroup)
  1131. } catch {
  1132. outError?.pointee = error as NSError
  1133. return nil
  1134. }
  1135. }
  1136. /// Get the stored user in the given accessGroup.
  1137. ///
  1138. /// This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`.
  1139. /// and will return `nil`.
  1140. ///
  1141. /// Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details.
  1142. open func getStoredUser(forAccessGroup accessGroup: String?) throws -> User? {
  1143. var user: User?
  1144. if let accessGroup {
  1145. #if os(tvOS)
  1146. if shareAuthStateAcrossDevices {
  1147. AuthLog.logError(code: "I-AUT000001",
  1148. message: "Getting a stored user for a given access group is not supported " +
  1149. "on tvOS when `shareAuthStateAcrossDevices` is set to `true` (#8878)." +
  1150. "This case will return `nil`.")
  1151. return nil
  1152. }
  1153. #endif
  1154. guard let apiKey = app?.options.apiKey else {
  1155. fatalError("Internal Auth Error: missing apiKey")
  1156. }
  1157. user = try storedUserManager.getStoredUser(
  1158. accessGroup: accessGroup,
  1159. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1160. projectIdentifier: apiKey
  1161. )
  1162. } else {
  1163. let userKey = "\(firebaseAppName)\(kUserKey)"
  1164. if let encodedUserData = try keychainServices.data(forKey: userKey) {
  1165. let unarchiver = try NSKeyedUnarchiver(forReadingFrom: encodedUserData)
  1166. user = unarchiver.decodeObject(of: User.self, forKey: userKey)
  1167. }
  1168. }
  1169. user?.auth = self
  1170. return user
  1171. }
  1172. /// Sets the `currentUser` on the receiver to the provided user object.
  1173. /// - Parameters:
  1174. /// - user: The user object to be set as the current user of the calling Auth instance.
  1175. /// - completion: Optionally; a block invoked after the user of the calling Auth instance has
  1176. /// been updated or an error was encountered.
  1177. @objc open func updateCurrentUser(_ user: User?, completion: ((Error?) -> Void)? = nil) {
  1178. Task {
  1179. guard let user else {
  1180. await MainActor.run {
  1181. completion?(AuthErrorUtils.nullUserError(message: nil))
  1182. }
  1183. return
  1184. }
  1185. do {
  1186. try await updateCurrentUser(user)
  1187. await MainActor.run {
  1188. completion?(nil)
  1189. }
  1190. } catch {
  1191. await MainActor.run {
  1192. completion?(error)
  1193. }
  1194. }
  1195. }
  1196. }
  1197. /// Sets the `currentUser` on the receiver to the provided user object.
  1198. /// - Parameter user: The user object to be set as the current user of the calling Auth instance.
  1199. /// - Parameter completion: Optionally; a block invoked after the user of the calling Auth
  1200. /// instance has been updated or an error was encountered.
  1201. open func updateCurrentUser(_ user: User) async throws {
  1202. try await authWorker.updateCurrentUser(user)
  1203. }
  1204. #if os(iOS)
  1205. /// The APNs token used for phone number authentication.
  1206. ///
  1207. /// The type of the token (production or sandbox) will be automatically
  1208. /// detected based on your provisioning profile.
  1209. ///
  1210. /// This property is available on iOS only.
  1211. ///
  1212. /// If swizzling is disabled, the APNs Token must be set for phone number auth to work,
  1213. /// by either setting this property or by calling `setAPNSToken(_:type:)`.
  1214. @objc(APNSToken) open var apnsToken: Data? {
  1215. var data: Data?
  1216. let semaphore = DispatchSemaphore(value: 0)
  1217. Task {
  1218. data = await tokenManagerGet().token?.data
  1219. semaphore.signal()
  1220. }
  1221. semaphore.wait()
  1222. return data
  1223. }
  1224. func tokenManagerInit(_ manager: AuthAPNSTokenManager) {
  1225. let semaphore = DispatchSemaphore(value: 0)
  1226. Task {
  1227. await authWorker.tokenManagerInit(manager)
  1228. semaphore.signal()
  1229. }
  1230. semaphore.wait()
  1231. }
  1232. func tokenManagerInit(_ manager: AuthAPNSTokenManager) async {
  1233. await authWorker.tokenManagerInit(manager)
  1234. }
  1235. func tokenManagerGet() -> AuthAPNSTokenManager {
  1236. var manager: AuthAPNSTokenManager!
  1237. let semaphore = DispatchSemaphore(value: 0)
  1238. Task {
  1239. manager = await tokenManagerGet()
  1240. semaphore.signal()
  1241. }
  1242. semaphore.wait()
  1243. return manager
  1244. }
  1245. func tokenManagerGet() async -> AuthAPNSTokenManager {
  1246. return await authWorker.tokenManagerGet()
  1247. }
  1248. /// Sets the APNs token along with its type.
  1249. ///
  1250. /// This method is available on iOS only.
  1251. ///
  1252. /// If swizzling is disabled, the APNs Token must be set for phone number auth to work,
  1253. /// by either setting calling this method or by setting the `APNSToken` property.
  1254. @objc open func setAPNSToken(_ token: Data, type: AuthAPNSTokenType) {
  1255. let semaphore = DispatchSemaphore(value: 0)
  1256. Task {
  1257. await authWorker.tokenManagerSet(token, type: type)
  1258. semaphore.signal()
  1259. }
  1260. semaphore.wait()
  1261. }
  1262. /// Sets the APNs token along with its type.
  1263. ///
  1264. /// This method is available on iOS only.
  1265. ///
  1266. /// If swizzling is disabled, the APNs Token must be set for phone number auth to work,
  1267. /// by either setting calling this method or by setting the `APNSToken` property.
  1268. open func setAPNSToken(_ token: Data, type: AuthAPNSTokenType) async {
  1269. await authWorker.tokenManagerSet(token, type: type)
  1270. }
  1271. /// Whether the specific remote notification is handled by `Auth` .
  1272. ///
  1273. /// This method is available on iOS only.
  1274. ///
  1275. /// If swizzling is disabled, related remote notifications must be forwarded to this method
  1276. /// for phone number auth to work.
  1277. /// - Parameter userInfo: A dictionary that contains information related to the
  1278. /// notification in question.
  1279. /// - Returns: Whether or the notification is handled. A return value of `true` means the
  1280. /// notification is for Firebase Auth so the caller should ignore the notification from further
  1281. /// processing, and `false` means the notification is for the app (or another library) so
  1282. /// the caller should continue handling this notification as usual.
  1283. @objc open func canHandleNotification(_ userInfo: [AnyHashable: Any]) -> Bool {
  1284. var result = false
  1285. let semaphore = DispatchSemaphore(value: 0)
  1286. Task {
  1287. result = await authWorker.canHandleNotification(userInfo)
  1288. semaphore.signal()
  1289. }
  1290. semaphore.wait()
  1291. return result
  1292. }
  1293. /// Whether the specific remote notification is handled by `Auth` .
  1294. ///
  1295. /// This method is available on iOS only.
  1296. ///
  1297. /// If swizzling is disabled, related remote notifications must be forwarded to this method
  1298. /// for phone number auth to work.
  1299. /// - Parameter userInfo: A dictionary that contains information related to the
  1300. /// notification in question.
  1301. /// - Returns: Whether or the notification is handled. A return value of `true` means the
  1302. /// notification is for Firebase Auth so the caller should ignore the notification from further
  1303. /// processing, and `false` means the notification is for the app (or another library) so
  1304. /// the caller should continue handling this notification as usual.
  1305. @objc open func canHandleNotification(_ userInfo: [AnyHashable: Any]) async -> Bool {
  1306. return await authWorker.canHandleNotification(userInfo)
  1307. }
  1308. /// Whether the specific URL is handled by `Auth` .
  1309. ///
  1310. /// This method is available on iOS only.
  1311. ///
  1312. /// If swizzling is disabled, URLs received by the application delegate must be forwarded
  1313. /// to this method for phone number auth to work.
  1314. /// - Parameter url: The URL received by the application delegate from any of the openURL
  1315. /// method.
  1316. /// - Returns: Whether or the URL is handled. `true` means the URL is for Firebase Auth
  1317. /// so the caller should ignore the URL from further processing, and `false` means the
  1318. /// the URL is for the app (or another library) so the caller should continue handling
  1319. /// this URL as usual.
  1320. @objc(canHandleURL:) open func canHandle(_ url: URL) -> Bool {
  1321. var result = false
  1322. let semaphore = DispatchSemaphore(value: 0)
  1323. Task {
  1324. result = await authWorker.canHandle(url)
  1325. semaphore.signal()
  1326. }
  1327. semaphore.wait()
  1328. return result
  1329. }
  1330. /// Whether the specific URL is handled by `Auth` .
  1331. ///
  1332. /// This method is available on iOS only.
  1333. ///
  1334. /// If swizzling is disabled, URLs received by the application delegate must be forwarded
  1335. /// to this method for phone number auth to work.
  1336. /// - Parameter url: The URL received by the application delegate from any of the openURL
  1337. /// method.
  1338. /// - Returns: Whether or the URL is handled. `true` means the URL is for Firebase Auth
  1339. /// so the caller should ignore the URL from further processing, and `false` means the
  1340. /// the URL is for the app (or another library) so the caller should continue handling
  1341. /// this URL as usual.
  1342. open func canHandle(_ url: URL) async -> Bool {
  1343. return await authWorker.canHandle(url)
  1344. }
  1345. #endif
  1346. /// The name of the `NSNotificationCenter` notification which is posted when the auth state
  1347. /// changes (for example, a new token has been produced, a user signs in or signs out).
  1348. ///
  1349. /// The object parameter of the notification is the sender `Auth` instance.
  1350. public static let authStateDidChangeNotification =
  1351. NSNotification.Name(rawValue: "FIRAuthStateDidChangeNotification")
  1352. // MARK: Internal methods
  1353. init(app: FirebaseApp, keychainStorageProvider: AuthKeychainStorage = AuthKeychainStorageReal()) {
  1354. Auth.setKeychainServiceNameForApp(app)
  1355. self.app = app
  1356. mainBundleUrlTypes = Bundle.main
  1357. .object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]]
  1358. let appCheck = ComponentType<AppCheckInterop>.instance(for: AppCheckInterop.self,
  1359. in: app.container)
  1360. guard let apiKey = app.options.apiKey else {
  1361. fatalError("Missing apiKey for Auth initialization")
  1362. }
  1363. firebaseAppName = app.name
  1364. #if os(iOS)
  1365. authURLPresenter = AuthURLPresenter()
  1366. settings = AuthSettings()
  1367. GULAppDelegateSwizzler.proxyOriginalDelegateIncludingAPNSMethods()
  1368. GULSceneDelegateSwizzler.proxyOriginalSceneDelegate()
  1369. #endif
  1370. requestConfiguration = AuthRequestConfiguration(apiKey: apiKey,
  1371. appID: app.options.googleAppID,
  1372. auth: nil,
  1373. heartbeatLogger: app.heartbeatLogger,
  1374. appCheck: appCheck)
  1375. authWorker = AuthWorker(requestConfiguration: requestConfiguration)
  1376. super.init()
  1377. requestConfiguration.auth = self
  1378. Task {
  1379. await authWorker.protectedDataInitialization(keychainStorageProvider)
  1380. }
  1381. }
  1382. // TODO: delete me
  1383. func signInFlowAuthDataResultCallback(byDecorating callback:
  1384. ((AuthDataResult?, Error?) -> Void)?) -> (AuthDataResult?, Error?) -> Void {
  1385. let authDataCallback: (((AuthDataResult?, Error?) -> Void)?, AuthDataResult?, Error?) -> Void =
  1386. { callback, result, error in
  1387. Auth.wrapMainAsync(callback: callback, withParam: result, error: error)
  1388. }
  1389. return { authResult, error in
  1390. if let error {
  1391. authDataCallback(callback, nil, error)
  1392. return
  1393. }
  1394. do {
  1395. try self.updateCurrentUser(authResult?.user, byForce: false, savingToDisk: true)
  1396. } catch {
  1397. authDataCallback(callback, nil, error)
  1398. return
  1399. }
  1400. authDataCallback(callback, authResult, nil)
  1401. }
  1402. }
  1403. // TODO: delete me
  1404. func updateCurrentUser(_ user: User?, byForce force: Bool,
  1405. savingToDisk saveToDisk: Bool) throws {
  1406. if user == currentUser {
  1407. possiblyPostAuthStateChangeNotification()
  1408. }
  1409. if let user {
  1410. if user.tenantID != nil || tenantID != nil, tenantID != user.tenantID {
  1411. let error = AuthErrorUtils.tenantIDMismatchError()
  1412. throw error
  1413. }
  1414. }
  1415. var throwError: Error?
  1416. if saveToDisk {
  1417. do {
  1418. try saveUser(user)
  1419. } catch {
  1420. throwError = error
  1421. }
  1422. }
  1423. if throwError == nil || force {
  1424. currentUser = user
  1425. possiblyPostAuthStateChangeNotification()
  1426. }
  1427. if let throwError {
  1428. throw throwError
  1429. }
  1430. }
  1431. #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
  1432. func addProtectedDataDidBecomeAvailableObserver() {
  1433. weak var weakSelf = self
  1434. protectedDataDidBecomeAvailableObserver =
  1435. NotificationCenter.default.addObserver(
  1436. forName: UIApplication.protectedDataDidBecomeAvailableNotification,
  1437. object: nil,
  1438. queue: nil
  1439. ) { notification in
  1440. let strongSelf = weakSelf
  1441. if let observer = strongSelf?.protectedDataDidBecomeAvailableObserver {
  1442. NotificationCenter.default.removeObserver(
  1443. observer,
  1444. name: UIApplication.protectedDataDidBecomeAvailableNotification,
  1445. object: nil
  1446. )
  1447. }
  1448. }
  1449. }
  1450. #endif
  1451. deinit {
  1452. let defaultCenter = NotificationCenter.default
  1453. while listenerHandles.count > 0 {
  1454. let handleToRemove = listenerHandles.lastObject
  1455. defaultCenter.removeObserver(handleToRemove as Any)
  1456. listenerHandles.removeLastObject()
  1457. }
  1458. #if os(iOS)
  1459. defaultCenter.removeObserver(applicationDidBecomeActiveObserver as Any,
  1460. name: UIApplication.didBecomeActiveNotification,
  1461. object: nil)
  1462. defaultCenter.removeObserver(applicationDidEnterBackgroundObserver as Any,
  1463. name: UIApplication.didEnterBackgroundNotification,
  1464. object: nil)
  1465. #endif
  1466. }
  1467. func getUser() throws -> User? {
  1468. var user: User?
  1469. if let userAccessGroup {
  1470. guard let apiKey = app?.options.apiKey else {
  1471. fatalError("Internal Auth Error: missing apiKey")
  1472. }
  1473. user = try storedUserManager.getStoredUser(
  1474. accessGroup: userAccessGroup,
  1475. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1476. projectIdentifier: apiKey
  1477. )
  1478. } else {
  1479. let userKey = "\(firebaseAppName)\(kUserKey)"
  1480. guard let encodedUserData = try keychainServices.data(forKey: userKey) else {
  1481. return nil
  1482. }
  1483. let unarchiver = try NSKeyedUnarchiver(forReadingFrom: encodedUserData)
  1484. user = unarchiver.decodeObject(of: User.self, forKey: userKey)
  1485. }
  1486. user?.auth = self
  1487. return user
  1488. }
  1489. /// Gets the keychain service name global data for the particular app by name.
  1490. /// - Parameter appName: The name of the Firebase app to get keychain service name for.
  1491. class func keychainServiceForAppID(_ appID: String) -> String {
  1492. return "firebase_auth_\(appID)"
  1493. }
  1494. func updateKeychain(withUser user: User?) -> Error? {
  1495. if user != currentUser {
  1496. // No-op if the user is no longer signed in. This is not considered an error as we don't check
  1497. // whether the user is still current on other callbacks of user operations either.
  1498. return nil
  1499. }
  1500. do {
  1501. try saveUser(user)
  1502. possiblyPostAuthStateChangeNotification()
  1503. } catch {
  1504. return error
  1505. }
  1506. return nil
  1507. }
  1508. /// A map from Firebase app name to keychain service names.
  1509. ///
  1510. /// This map is needed for looking up the keychain service name after the FirebaseApp instance
  1511. /// is deleted, to remove the associated keychain item. Accessing should occur within a
  1512. /// @syncronized([FIRAuth class]) context.
  1513. fileprivate static var gKeychainServiceNameForAppName: [String: String] = [:]
  1514. /// Sets the keychain service name global data for the particular app.
  1515. /// - Parameter app: The Firebase app to set keychain service name for.
  1516. class func setKeychainServiceNameForApp(_ app: FirebaseApp) {
  1517. objc_sync_enter(Auth.self)
  1518. gKeychainServiceNameForAppName[app.name] = "firebase_auth_\(app.options.googleAppID)"
  1519. objc_sync_exit(Auth.self)
  1520. }
  1521. /// Gets the keychain service name global data for the particular app by name.
  1522. /// - Parameter appName: The name of the Firebase app to get keychain service name for.
  1523. class func keychainServiceName(forAppName appName: String) -> String? {
  1524. objc_sync_enter(Auth.self)
  1525. defer { objc_sync_exit(Auth.self) }
  1526. return gKeychainServiceNameForAppName[appName]
  1527. }
  1528. /// Deletes the keychain service name global data for the particular app by name.
  1529. /// - Parameter appName: The name of the Firebase app to delete keychain service name for.
  1530. class func deleteKeychainServiceNameForAppName(_ appName: String) {
  1531. objc_sync_enter(Auth.self)
  1532. gKeychainServiceNameForAppName.removeValue(forKey: appName)
  1533. objc_sync_exit(Auth.self)
  1534. }
  1535. func signOutByForce(withUserID userID: String) throws {
  1536. guard currentUser?.uid == userID else {
  1537. return
  1538. }
  1539. try updateCurrentUser(nil, byForce: true, savingToDisk: true)
  1540. }
  1541. // MARK: Private methods
  1542. /// Posts the auth state change notification if current user's token has been changed.
  1543. func possiblyPostAuthStateChangeNotification() {
  1544. let token = currentUser?.rawAccessToken()
  1545. if lastNotifiedUserToken == token ||
  1546. (token != nil && lastNotifiedUserToken == token) {
  1547. return
  1548. }
  1549. lastNotifiedUserToken = token
  1550. if autoRefreshTokens {
  1551. // Shedule new refresh task after successful attempt.
  1552. scheduleAutoTokenRefresh()
  1553. }
  1554. var internalNotificationParameters: [String: Any] = [:]
  1555. if let app = app {
  1556. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = app
  1557. }
  1558. if let token, token.count > 0 {
  1559. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token
  1560. }
  1561. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = currentUser?
  1562. .uid
  1563. let notifications = NotificationCenter.default
  1564. DispatchQueue.main.async {
  1565. notifications.post(name: NSNotification.Name.FIRAuthStateDidChangeInternal,
  1566. object: self,
  1567. userInfo: internalNotificationParameters)
  1568. notifications.post(name: Auth.authStateDidChangeNotification, object: self)
  1569. }
  1570. }
  1571. /// Schedules a task to automatically refresh tokens on the current user. The0 token refresh
  1572. /// is scheduled 5 minutes before the scheduled expiration time.
  1573. ///
  1574. /// If the token expires in less than 5 minutes, schedule the token refresh immediately.
  1575. func scheduleAutoTokenRefresh() {
  1576. let tokenExpirationInterval =
  1577. (currentUser?.accessTokenExpirationDate()?.timeIntervalSinceNow ?? 0) - 5 * 60
  1578. scheduleAutoTokenRefresh(withDelay: max(tokenExpirationInterval, 0), retry: false)
  1579. }
  1580. /// Schedules a task to automatically refresh tokens on the current user.
  1581. /// - Parameter delay: The delay in seconds after which the token refresh task should be scheduled
  1582. /// to be executed.
  1583. /// - Parameter retry: Flag to determine whether the invocation is a retry attempt or not.
  1584. func scheduleAutoTokenRefresh(withDelay delay: TimeInterval, retry: Bool) {
  1585. guard let accessToken = currentUser?.rawAccessToken() else {
  1586. return
  1587. }
  1588. let intDelay = Int(ceil(delay))
  1589. if retry {
  1590. AuthLog.logInfo(code: "I-AUT000003", message: "Token auto-refresh re-scheduled in " +
  1591. "\(intDelay / 60):\(intDelay % 60) " +
  1592. "because of error on previous refresh attempt.")
  1593. } else {
  1594. AuthLog.logInfo(code: "I-AUT000004", message: "Token auto-refresh scheduled in " +
  1595. "\(intDelay / 60):\(intDelay % 60) " +
  1596. "for the new token.")
  1597. }
  1598. autoRefreshScheduled = true
  1599. Task {
  1600. await authWorker.autoTokenRefresh(accessToken: accessToken,
  1601. retry: retry,
  1602. delay: fastTokenRefreshForTest ? 0.1 : delay)
  1603. }
  1604. }
  1605. func saveUser(_ user: User?) throws {
  1606. if let userAccessGroup {
  1607. guard let apiKey = app?.options.apiKey else {
  1608. fatalError("Internal Auth Error: Missing apiKey in saveUser")
  1609. }
  1610. if let user {
  1611. try storedUserManager.setStoredUser(user: user,
  1612. accessGroup: userAccessGroup,
  1613. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1614. projectIdentifier: apiKey)
  1615. } else {
  1616. try storedUserManager.removeStoredUser(
  1617. accessGroup: userAccessGroup,
  1618. shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
  1619. projectIdentifier: apiKey
  1620. )
  1621. }
  1622. } else {
  1623. let userKey = "\(firebaseAppName)\(kUserKey)"
  1624. if let user {
  1625. let archiver = NSKeyedArchiver(requiringSecureCoding: true)
  1626. archiver.encode(user, forKey: userKey)
  1627. archiver.finishEncoding()
  1628. let archiveData = archiver.encodedData
  1629. // Save the user object's encoded value.
  1630. try keychainServices.setData(archiveData as Data, forKey: userKey)
  1631. } else {
  1632. try keychainServices.removeData(forKey: userKey)
  1633. }
  1634. }
  1635. }
  1636. /// Completes a sign-in flow once we have access and refresh tokens for the user.
  1637. /// - Parameter accessToken: The STS access token.
  1638. /// - Parameter accessTokenExpirationDate: The approximate expiration date of the access token.
  1639. /// - Parameter refreshToken: The STS refresh token.
  1640. /// - Parameter anonymous: Whether or not the user is anonymous.
  1641. @discardableResult
  1642. func completeSignIn(withAccessToken accessToken: String?,
  1643. accessTokenExpirationDate: Date?,
  1644. refreshToken: String?,
  1645. anonymous: Bool) async throws -> User {
  1646. return try await User.retrieveUser(withAuth: self,
  1647. accessToken: accessToken,
  1648. accessTokenExpirationDate: accessTokenExpirationDate,
  1649. refreshToken: refreshToken,
  1650. anonymous: anonymous)
  1651. }
  1652. private func getQueryItems(_ link: String) -> [String: String] {
  1653. var queryItems = AuthWebUtils.parseURL(link)
  1654. if queryItems.count == 0 {
  1655. let urlComponents = URLComponents(string: link)
  1656. if let query = urlComponents?.query {
  1657. queryItems = AuthWebUtils.parseURL(query)
  1658. }
  1659. }
  1660. return queryItems
  1661. }
  1662. private func getLanguageCode() -> String? {
  1663. var code: String?
  1664. let semaphore = DispatchSemaphore(value: 0)
  1665. Task {
  1666. code = await authWorker.getLanguageCode()
  1667. semaphore.signal()
  1668. }
  1669. semaphore.wait()
  1670. return code
  1671. }
  1672. private func setLanguageCode(_ code: String?) {
  1673. let semaphore = DispatchSemaphore(value: 0)
  1674. Task {
  1675. await authWorker.setLanguageCode(code)
  1676. semaphore.signal()
  1677. }
  1678. semaphore.wait()
  1679. }
  1680. class func wrapMainAsync(_ callback: ((Error?) -> Void)?, _ error: Error?) {
  1681. if let callback {
  1682. DispatchQueue.main.async {
  1683. callback(error)
  1684. }
  1685. }
  1686. }
  1687. class func wrapMainAsync<T: Any>(callback: ((T?, Error?) -> Void)?,
  1688. withParam param: T?,
  1689. error: Error?) -> Void {
  1690. if let callback {
  1691. DispatchQueue.main.async {
  1692. callback(param, error)
  1693. }
  1694. }
  1695. }
  1696. // MARK: Internal properties
  1697. /// Allow tests to swap in an alternate mainBundle.
  1698. var mainBundleUrlTypes: [[String: Any]]!
  1699. /// The configuration object comprising of parameters needed to make a request to Firebase
  1700. /// Auth's backend.
  1701. var requestConfiguration: AuthRequestConfiguration
  1702. let authWorker: AuthWorker
  1703. var fastTokenRefreshForTest = false
  1704. #if os(iOS)
  1705. /// The manager for app credentials used by phone number auth.
  1706. var appCredentialManager: AuthAppCredentialManager!
  1707. /// The manager for remote notifications used by phone number auth.
  1708. var notificationManager: AuthNotificationManager!
  1709. /// An object that takes care of presenting URLs via the auth instance.
  1710. var authURLPresenter: AuthWebViewControllerDelegate
  1711. #endif // TARGET_OS_IOS
  1712. // MARK: Private properties
  1713. /// The stored user manager.
  1714. var storedUserManager: AuthStoredUserManager!
  1715. /// The Firebase app name.
  1716. let firebaseAppName: String
  1717. /// The keychain service.
  1718. var keychainServices: AuthKeychainServices!
  1719. /// The user access (ID) token used last time for posting auth state changed notification.
  1720. var lastNotifiedUserToken: String?
  1721. /// This flag denotes whether or not tokens should be automatically refreshed.
  1722. /// Will only be set to `true` if the another Firebase service is included (additionally to
  1723. /// Firebase Auth).
  1724. private var autoRefreshTokens = false
  1725. /// Whether or not token auto-refresh is currently scheduled.
  1726. var autoRefreshScheduled = false
  1727. /// A flag that is set to YES if the app is put in the background and no when the app is
  1728. /// returned to the foreground.
  1729. var isAppInBackground = false
  1730. /// An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification.
  1731. private var applicationDidBecomeActiveObserver: NSObjectProtocol?
  1732. /// An opaque object to act as the observer for
  1733. /// UIApplicationDidEnterBackgroundNotification.
  1734. private var applicationDidEnterBackgroundObserver: NSObjectProtocol?
  1735. /// An opaque object to act as the observer for
  1736. /// UIApplicationProtectedDataDidBecomeAvailable.
  1737. private var protectedDataDidBecomeAvailableObserver: NSObjectProtocol?
  1738. /// Key of user stored in the keychain. Prefixed with a Firebase app name.
  1739. private let kUserKey = "_firebase_user"
  1740. /// Handles returned from `NSNotificationCenter` for blocks which are "auth state did
  1741. /// change" notification listeners.
  1742. ///
  1743. /// Mutations should occur within a @syncronized(self) context.
  1744. private var listenerHandles: NSMutableArray = []
  1745. }