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