Auth.swift 105 KB

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