Auth.swift 106 KB

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