Auth.swift 109 KB

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