Auth.swift 108 KB

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