Auth.swift 108 KB

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