Auth.swift 105 KB

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