Auth.swift 106 KB

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