User.swift 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  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. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  16. extension User: NSSecureCoding {}
  17. /// Represents a user.
  18. ///
  19. /// Firebase Auth does not attempt to validate users
  20. /// when loading them from the keychain. Invalidated users (such as those
  21. /// whose passwords have been changed on another client) are automatically
  22. /// logged out when an auth-dependent operation is attempted or when the
  23. /// ID token is automatically refreshed.
  24. ///
  25. /// This class is thread-safe.
  26. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  27. @objc(FIRUser) open class User: NSObject, UserInfo {
  28. /// Indicates the user represents an anonymous user.
  29. @objc public internal(set) var isAnonymous: Bool
  30. /// Indicates the user represents an anonymous user.
  31. @objc open func anonymous() -> Bool { return isAnonymous }
  32. /// Indicates the email address associated with this user has been verified.
  33. @objc public private(set) var isEmailVerified: Bool
  34. /// Indicates the email address associated with this user has been verified.
  35. @objc open func emailVerified() -> Bool { return isEmailVerified }
  36. /// Profile data for each identity provider, if any.
  37. ///
  38. /// This data is cached on sign-in and updated when linking or unlinking.
  39. @objc open var providerData: [UserInfo] {
  40. return Array(providerDataRaw.values)
  41. }
  42. var providerDataRaw: [String: UserInfoImpl]
  43. /// Metadata associated with the Firebase user in question.
  44. @objc public private(set) var metadata: UserMetadata
  45. /// The tenant ID of the current user. `nil` if none is available.
  46. @objc public private(set) var tenantID: String?
  47. #if os(iOS)
  48. /// Multi factor object associated with the user.
  49. ///
  50. /// This property is available on iOS only.
  51. @objc public private(set) var multiFactor: MultiFactor
  52. #endif
  53. /// [Deprecated] Updates the email address for the user.
  54. ///
  55. /// On success, the cached user profile data is updated. Returns an error when
  56. /// [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  57. /// is enabled.
  58. ///
  59. /// May fail if there is already an account with this email address that was created using
  60. /// email and password authentication.
  61. ///
  62. /// Invoked asynchronously on the main thread in the future.
  63. ///
  64. /// Possible error codes:
  65. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  66. /// sent in the request.
  67. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  68. /// the console for this action.
  69. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  70. /// sending update email.
  71. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another
  72. /// account.
  73. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  74. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security
  75. /// sensitive operation that requires a recent login from the user. This error indicates
  76. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  77. /// calling `reauthenticate(with:)`.
  78. /// - Parameter email: The email address for the user.
  79. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  80. /// finished.
  81. #if !FIREBASE_CI
  82. @available(
  83. *,
  84. deprecated,
  85. message: "`updateEmail` is deprecated and will be removed in a future release. Use sendEmailVerification(beforeUpdatingEmail:) instead."
  86. )
  87. #endif // !FIREBASE_CI
  88. @objc(updateEmail:completion:)
  89. open func updateEmail(to email: String, completion: ((Error?) -> Void)? = nil) {
  90. Task {
  91. do {
  92. try await auth.authWorker.updateEmail(user: self, email: email, password: nil)
  93. await MainActor.run {
  94. completion?(nil)
  95. }
  96. } catch {
  97. await MainActor.run {
  98. completion?(error)
  99. }
  100. }
  101. }
  102. }
  103. /// [Deprecated] Updates the email address for the user.
  104. ///
  105. /// On success, the cached user profile data is updated. Throws when
  106. /// [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  107. /// is enabled.
  108. ///
  109. /// May fail if there is already an account with this email address that was created using
  110. /// email and password authentication.
  111. ///
  112. /// Invoked asynchronously on the main thread in the future.
  113. ///
  114. /// Possible error codes:
  115. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  116. /// sent in the request.
  117. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  118. /// the console for this action.
  119. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  120. /// sending update email.
  121. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another
  122. /// account.
  123. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  124. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security
  125. /// sensitive operation that requires a recent login from the user. This error indicates
  126. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  127. /// calling `reauthenticate(with:)`.
  128. /// - Parameter email: The email address for the user.
  129. #if !FIREBASE_CI
  130. @available(
  131. *,
  132. deprecated,
  133. message: "`updateEmail` is deprecated and will be removed in a future release. Use sendEmailVerification(beforeUpdatingEmail:) instead."
  134. )
  135. #endif // !FIREBASE_CI
  136. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  137. open func updateEmail(to email: String) async throws {
  138. try await auth.authWorker.updateEmail(user: self, email: email, password: nil)
  139. }
  140. /// Updates the password for the user. On success, the cached user profile data is updated.
  141. ///
  142. /// Invoked asynchronously on the main thread in the future.
  143. ///
  144. /// Possible error codes:
  145. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled
  146. /// sign in with the specified identity provider.
  147. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security
  148. /// sensitive operation that requires a recent login from the user. This error indicates
  149. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  150. /// calling `reauthenticate(with:)`.
  151. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  152. /// considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  153. /// dictionary object will contain more detailed explanation that can be shown to the user.
  154. /// - Parameter password: The new password for the user.
  155. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  156. /// finished.
  157. @objc(updatePassword:completion:)
  158. open func updatePassword(to password: String, completion: ((Error?) -> Void)? = nil) {
  159. Task {
  160. do {
  161. try await self.updatePassword(to: password)
  162. await MainActor.run {
  163. completion?(nil)
  164. }
  165. } catch {
  166. await MainActor.run {
  167. completion?(error)
  168. }
  169. }
  170. }
  171. }
  172. /// Updates the password for the user. On success, the cached user profile data is updated.
  173. ///
  174. /// Invoked asynchronously on the main thread in the future.
  175. ///
  176. /// Possible error codes:
  177. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled
  178. /// sign in with the specified identity provider.
  179. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security
  180. /// sensitive operation that requires a recent login from the user. This error indicates
  181. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  182. /// calling `reauthenticate(with:)`.
  183. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  184. /// considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  185. /// dictionary object will contain more detailed explanation that can be shown to the user.
  186. /// - Parameter password: The new password for the user.
  187. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  188. open func updatePassword(to password: String) async throws {
  189. guard password.count > 0 else {
  190. throw AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing Password")
  191. }
  192. return try await auth.authWorker.updateEmail(user: self, email: nil, password: password)
  193. }
  194. #if os(iOS)
  195. /// Updates the phone number for the user. On success, the cached user profile data is updated.
  196. ///
  197. /// Invoked asynchronously on the main thread in the future.
  198. ///
  199. /// This method is available on iOS only.
  200. ///
  201. /// Possible error codes:
  202. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security
  203. /// sensitive operation that requires a recent login from the user. This error indicates
  204. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  205. /// calling `reauthenticate(with:)`.
  206. /// - Parameter credential: The new phone number credential corresponding to the
  207. /// phone number to be added to the Firebase account, if a phone number is already linked to the
  208. /// account this new phone number will replace it.
  209. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  210. /// finished.
  211. @objc(updatePhoneNumberCredential:completion:)
  212. open func updatePhoneNumber(_ credential: PhoneAuthCredential,
  213. completion: ((Error?) -> Void)? = nil) {
  214. Task {
  215. do {
  216. try await self.updatePhoneNumber(credential)
  217. await MainActor.run {
  218. completion?(nil)
  219. }
  220. } catch {
  221. await MainActor.run {
  222. completion?(error)
  223. }
  224. }
  225. }
  226. }
  227. /// Updates the phone number for the user. On success, the cached user profile data is updated.
  228. ///
  229. /// Invoked asynchronously on the main thread in the future.
  230. ///
  231. /// This method is available on iOS only.
  232. ///
  233. /// Possible error codes:
  234. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security
  235. /// sensitive operation that requires a recent login from the user. This error indicates
  236. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  237. /// calling `reauthenticate(with:)`.
  238. /// - Parameter phoneNumberCredential: The new phone number credential corresponding to the
  239. /// phone number to be added to the Firebase account, if a phone number is already linked to the
  240. /// account this new phone number will replace it.
  241. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  242. open func updatePhoneNumber(_ credential: PhoneAuthCredential) async throws {
  243. try await auth.authWorker.updateOrLinkPhoneNumber(user: self,
  244. credential: credential,
  245. isLinkOperation: false)
  246. }
  247. #endif
  248. /// Creates an object which may be used to change the user's profile data.
  249. ///
  250. /// Set the properties of the returned object, then call
  251. /// `UserProfileChangeRequest.commitChanges()` to perform the updates atomically.
  252. /// - Returns: An object which may be used to change the user's profile data atomically.
  253. @objc(profileChangeRequest)
  254. open func createProfileChangeRequest() -> UserProfileChangeRequest {
  255. var result: UserProfileChangeRequest!
  256. kAuthGlobalWorkQueue.sync {
  257. result = UserProfileChangeRequest(self)
  258. }
  259. return result
  260. }
  261. /// A refresh token; useful for obtaining new access tokens independently.
  262. ///
  263. /// This property should only be used for advanced scenarios, and is not typically needed.
  264. @objc open var refreshToken: String? {
  265. var result: String?
  266. kAuthGlobalWorkQueue.sync {
  267. result = self.tokenService.refreshToken
  268. }
  269. return result
  270. }
  271. /// Reloads the user's profile data from the server.
  272. ///
  273. /// May fail with an `AuthErrorCodeRequiresRecentLogin` error code. In this case
  274. /// you should call `reauthenticate(with:)` before re-invoking
  275. /// `updateEmail(to:)`.
  276. /// - Parameter completion: Optionally; the block invoked when the reload has finished. Invoked
  277. /// asynchronously on the main thread in the future.
  278. @objc open func reload(completion: ((Error?) -> Void)? = nil) {
  279. Task {
  280. do {
  281. try await self.reload()
  282. await MainActor.run {
  283. completion?(nil)
  284. }
  285. } catch {
  286. await MainActor.run {
  287. completion?(error)
  288. }
  289. }
  290. }
  291. }
  292. /// Reloads the user's profile data from the server.
  293. ///
  294. /// May fail with an `AuthErrorCodeRequiresRecentLogin` error code. In this case
  295. /// you should call `reauthenticate(with:)` before re-invoking
  296. /// `updateEmail(to:)`.
  297. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  298. open func reload() async throws {
  299. let _ = try await auth.authWorker.getAccountInfoRefreshingCache(self)
  300. }
  301. /// Renews the user's authentication tokens by validating a fresh set of credentials supplied
  302. /// by the user and returns additional identity provider data.
  303. ///
  304. /// If the user associated with the supplied credential is different from the current user,
  305. /// or if the validation of the supplied credentials fails; an error is returned and the current
  306. /// user remains signed in.
  307. ///
  308. /// Possible error codes:
  309. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  310. /// This could happen if it has expired or it is malformed.
  311. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the
  312. /// identity provider represented by the credential are not enabled. Enable them in the
  313. /// Auth section of the Firebase console.
  314. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential
  315. /// (e.g. the email in a Facebook access token) is already in use by an existing account,
  316. /// that cannot be authenticated with this method. This error will only be thrown if the
  317. /// "One account per email address" setting is enabled in the Firebase console, under Auth
  318. /// settings. Please note that the error code raised in this specific situation may not be
  319. /// the same on Web and Android.
  320. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  321. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with
  322. /// an incorrect password, if credential is of the type `EmailPasswordAuthCredential`.
  323. /// * `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to
  324. /// reauthenticate with a user which is not the current user.
  325. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  326. /// - Parameter credential: A user-supplied credential, which will be validated by the server.
  327. /// This can be a successful third-party identity provider sign-in, or an email address and
  328. /// password.
  329. /// - Parameter completion: Optionally; the block invoked when the re-authentication operation has
  330. /// finished. Invoked asynchronously on the main thread in the future.
  331. @objc(reauthenticateWithCredential:completion:)
  332. open func reauthenticate(with credential: AuthCredential,
  333. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  334. Task {
  335. do {
  336. let result = try await reauthenticate(with: credential)
  337. await MainActor.run {
  338. completion?(result, nil)
  339. }
  340. } catch {
  341. await MainActor.run {
  342. completion?(nil, error)
  343. }
  344. }
  345. }
  346. }
  347. /// Renews the user's authentication tokens by validating a fresh set of credentials supplied
  348. /// by the user and returns additional identity provider data.
  349. ///
  350. /// If the user associated with the supplied credential is different from the current user,
  351. /// or if the validation of the supplied credentials fails; an error is returned and the current
  352. /// user remains signed in.
  353. ///
  354. /// Possible error codes:
  355. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  356. /// This could happen if it has expired or it is malformed.
  357. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the
  358. /// identity provider represented by the credential are not enabled. Enable them in the
  359. /// Auth section of the Firebase console.
  360. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential
  361. /// (e.g. the email in a Facebook access token) is already in use by an existing account,
  362. /// that cannot be authenticated with this method. This error will only be thrown if the
  363. /// "One account per email address" setting is enabled in the Firebase console, under Auth
  364. /// settings. Please note that the error code raised in this specific situation may not be
  365. /// the same on Web and Android.
  366. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  367. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with
  368. /// an incorrect password, if credential is of the type `EmailPasswordAuthCredential`.
  369. /// * `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to
  370. /// reauthenticate with a user which is not the current user.
  371. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  372. /// - Parameter credential: A user-supplied credential, which will be validated by the server.
  373. /// This can be a successful third-party identity provider sign-in, or an email address and
  374. /// password.
  375. /// - Returns: The `AuthDataResult` after the reauthentication.
  376. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  377. @discardableResult
  378. open func reauthenticate(with credential: AuthCredential) async throws -> AuthDataResult {
  379. return try await auth.authWorker.reauthenticate(with: credential)
  380. }
  381. #if os(iOS)
  382. /// Renews the user's authentication using the provided auth provider instance.
  383. ///
  384. /// This method is available on iOS only.
  385. /// - Parameter provider: An instance of an auth provider used to initiate the reauthenticate
  386. /// flow.
  387. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  388. /// protocol, used for presenting the web context. If nil, a default `AuthUIDelegate`
  389. /// will be used.
  390. /// - Parameter completion: Optionally; a block which is invoked when the reauthenticate flow
  391. /// finishes, or is canceled. Invoked asynchronously on the main thread in the future.
  392. @objc(reauthenticateWithProvider:UIDelegate:completion:)
  393. open func reauthenticate(with provider: FederatedAuthProvider,
  394. uiDelegate: AuthUIDelegate?,
  395. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  396. Task {
  397. do {
  398. let result = try await reauthenticate(with: provider, uiDelegate: uiDelegate)
  399. await MainActor.run {
  400. completion?(result, nil)
  401. }
  402. } catch {
  403. await MainActor.run {
  404. completion?(nil, error)
  405. }
  406. }
  407. }
  408. }
  409. /// Renews the user's authentication using the provided auth provider instance.
  410. ///
  411. /// This method is available on iOS only.
  412. /// - Parameter provider: An instance of an auth provider used to initiate the reauthenticate
  413. /// flow.
  414. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  415. /// protocol, used for presenting the web context. If nil, a default `AuthUIDelegate`
  416. /// will be used.
  417. /// - Returns: The `AuthDataResult` after the reauthentication.
  418. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  419. @discardableResult
  420. open func reauthenticate(with provider: FederatedAuthProvider,
  421. uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
  422. return try await auth.authWorker.reauthenticate(with: provider, uiDelegate: uiDelegate)
  423. }
  424. #endif
  425. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  426. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  427. /// asynchronously on the main thread in the future.
  428. @objc(getIDTokenWithCompletion:)
  429. open func getIDToken(completion: ((String?, Error?) -> Void)?) {
  430. // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to
  431. // global work queue here.
  432. getIDTokenForcingRefresh(false, completion: completion)
  433. }
  434. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  435. ///
  436. /// The authentication token will be refreshed (by making a network request) if it has
  437. /// expired, or if `forceRefresh` is `true`.
  438. /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some
  439. /// reason other than an expiration.
  440. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  441. /// asynchronously on the main thread in the future.
  442. @objc(getIDTokenForcingRefresh:completion:)
  443. open func getIDTokenForcingRefresh(_ forceRefresh: Bool,
  444. completion: ((String?, Error?) -> Void)?) {
  445. Task {
  446. do {
  447. let tokenResult = try await getIDTokenResult(forcingRefresh: forceRefresh)
  448. await MainActor.run {
  449. completion?(tokenResult.token, nil)
  450. }
  451. } catch {
  452. await MainActor.run {
  453. completion?(nil, error)
  454. }
  455. }
  456. }
  457. }
  458. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  459. ///
  460. /// The authentication token will be refreshed (by making a network request) if it has
  461. /// expired, or if `forceRefresh` is `true`.
  462. /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some
  463. /// reason other than an expiration.
  464. /// - Returns: The Firebase authentication token.
  465. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  466. open func getIDToken(forcingRefresh forceRefresh: Bool = false) async throws -> String {
  467. return try await getIDTokenResult(forcingRefresh: forceRefresh).token
  468. }
  469. /// API included for compatibility with a mis-named Firebase 10 API.
  470. /// Use `getIDToken(forcingRefresh forceRefresh: Bool = false)` instead.
  471. open func idTokenForcingRefresh(_ forceRefresh: Bool) async throws -> String {
  472. return try await getIDToken(forcingRefresh: forceRefresh)
  473. }
  474. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  475. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  476. /// asynchronously on the main thread in the future.
  477. @objc(getIDTokenResultWithCompletion:)
  478. open func getIDTokenResult(completion: ((AuthTokenResult?, Error?) -> Void)?) {
  479. getIDTokenResult(forcingRefresh: false, completion: completion)
  480. }
  481. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  482. ///
  483. /// The authentication token will be refreshed (by making a network request) if it has
  484. /// expired, or if `forcingRefresh` is `true`.
  485. /// - Parameter forcingRefresh: Forces a token refresh. Useful if the token becomes invalid for
  486. /// some
  487. /// reason other than an expiration.
  488. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  489. /// asynchronously on the main thread in the future.
  490. @objc(getIDTokenResultForcingRefresh:completion:)
  491. open func getIDTokenResult(forcingRefresh: Bool,
  492. completion: ((AuthTokenResult?, Error?) -> Void)?) {
  493. Task {
  494. do {
  495. let tokenResult = try await getIDTokenResult(forcingRefresh: forcingRefresh)
  496. await MainActor.run {
  497. completion?(tokenResult, nil)
  498. }
  499. } catch {
  500. await MainActor.run {
  501. completion?(nil, error)
  502. }
  503. }
  504. }
  505. }
  506. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  507. ///
  508. /// The authentication token will be refreshed (by making a network request) if it has
  509. /// expired, or if `forceRefresh` is `true`.
  510. /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some
  511. /// reason other than an expiration.
  512. /// - Returns: The Firebase authentication token.
  513. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  514. open func getIDTokenResult(forcingRefresh forceRefresh: Bool = false) async throws
  515. -> AuthTokenResult {
  516. try await auth.authWorker.getIDTokenResult(user: self, forcingRefresh: forceRefresh)
  517. }
  518. /// Associates a user account from a third-party identity provider with this user and
  519. /// returns additional identity provider data.
  520. ///
  521. /// Invoked asynchronously on the main thread in the future.
  522. ///
  523. /// Possible error codes:
  524. /// * `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a
  525. /// type already linked to this account.
  526. /// * `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a
  527. /// credential that has already been linked with a different Firebase account.
  528. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity
  529. /// provider represented by the credential are not enabled. Enable them in the Auth section
  530. /// of the Firebase console.
  531. ///
  532. /// This method may also return error codes associated with `updateEmail(to:)` and
  533. /// `updatePassword(to:)` on `User`.
  534. /// - Parameter credential: The credential for the identity provider.
  535. /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or
  536. /// fails.
  537. @objc(linkWithCredential:completion:)
  538. open func link(with credential: AuthCredential,
  539. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  540. Task {
  541. do {
  542. let tokenResult = try await link(with: credential)
  543. await MainActor.run {
  544. completion?(tokenResult, nil)
  545. }
  546. } catch {
  547. await MainActor.run {
  548. completion?(nil, error)
  549. }
  550. }
  551. }
  552. }
  553. /// Associates a user account from a third-party identity provider with this user and
  554. /// returns additional identity provider data.
  555. ///
  556. /// Invoked asynchronously on the main thread in the future.
  557. ///
  558. /// Possible error codes:
  559. /// * `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a
  560. /// type already linked to this account.
  561. /// * `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a
  562. /// credential that has already been linked with a different Firebase account.
  563. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity
  564. /// provider represented by the credential are not enabled. Enable them in the Auth section
  565. /// of the Firebase console.
  566. ///
  567. /// This method may also return error codes associated with `updateEmail(to:)` and
  568. /// `updatePassword(to:)` on `User`.
  569. /// - Parameter credential: The credential for the identity provider.
  570. /// - Returns: An `AuthDataResult`.
  571. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  572. @discardableResult
  573. open func link(with credential: AuthCredential) async throws -> AuthDataResult {
  574. return try await auth.authWorker.link(user: self, with: credential)
  575. }
  576. #if os(iOS)
  577. /// Link the user with the provided auth provider instance.
  578. ///
  579. /// This method is available on iOSonly.
  580. /// - Parameter provider: An instance of an auth provider used to initiate the link flow.
  581. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  582. /// protocol used for presenting the web context. If nil, a default `AuthUIDelegate` will be
  583. /// used.
  584. /// - Parameter completion: Optionally; a block which is invoked when the link flow finishes, or
  585. /// is canceled. Invoked asynchronously on the main thread in the future.
  586. @objc(linkWithProvider:UIDelegate:completion:)
  587. open func link(with provider: FederatedAuthProvider,
  588. uiDelegate: AuthUIDelegate?,
  589. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  590. Task {
  591. do {
  592. let tokenResult = try await link(with: provider, uiDelegate: uiDelegate)
  593. await MainActor.run {
  594. completion?(tokenResult, nil)
  595. }
  596. } catch {
  597. await MainActor.run {
  598. completion?(nil, error)
  599. }
  600. }
  601. }
  602. }
  603. /// Link the user with the provided auth provider instance.
  604. ///
  605. /// This method is available on iOSonly.
  606. /// - Parameter provider: An instance of an auth provider used to initiate the link flow.
  607. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  608. /// protocol used for presenting the web context. If nil, a default `AuthUIDelegate`
  609. /// will be used.
  610. /// - Parameter completion: Optionally; a block which is invoked when the link flow finishes, or
  611. /// is canceled. Invoked asynchronously on the main thread in the future.
  612. /// - Returns: An AuthDataResult.
  613. @discardableResult
  614. open func link(with provider: FederatedAuthProvider,
  615. uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
  616. return try await auth.authWorker.link(user: self, with: provider, uiDelegate: uiDelegate)
  617. }
  618. #endif
  619. /// Disassociates a user account from a third-party identity provider with this user.
  620. ///
  621. /// Invoked asynchronously on the main thread in the future.
  622. ///
  623. /// Possible error codes:
  624. /// * `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider
  625. /// that is not linked to the account.
  626. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  627. /// operation that requires a recent login from the user. This error indicates the user
  628. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  629. /// `reauthenticate(with:)`.
  630. /// - Parameter provider: The provider ID of the provider to unlink.
  631. /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or
  632. /// fails.
  633. @objc open func unlink(fromProvider provider: String,
  634. completion: ((User?, Error?) -> Void)? = nil) {
  635. Task {
  636. do {
  637. let user = try await unlink(fromProvider: provider)
  638. await MainActor.run {
  639. completion?(user, nil)
  640. }
  641. } catch {
  642. await MainActor.run {
  643. completion?(nil, error)
  644. }
  645. }
  646. }
  647. }
  648. /// Disassociates a user account from a third-party identity provider with this user.
  649. ///
  650. /// Invoked asynchronously on the main thread in the future.
  651. ///
  652. /// Possible error codes:
  653. /// * `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider
  654. /// that is not linked to the account.
  655. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  656. /// operation that requires a recent login from the user. This error indicates the user
  657. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  658. /// `reauthenticate(with:)`.
  659. /// - Parameter provider: The provider ID of the provider to unlink.
  660. /// - Returns: The user.
  661. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  662. open func unlink(fromProvider provider: String) async throws -> User {
  663. return try await auth.authWorker.unlink(user: self, fromProvider: provider)
  664. }
  665. /// Initiates email verification for the user.
  666. ///
  667. /// Possible error codes:
  668. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  669. /// sent in the request.
  670. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  671. /// the console for this action.
  672. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  673. /// sending update email.
  674. /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
  675. /// - Parameter completion: Optionally; the block invoked when the request to send an email
  676. /// verification is complete, or fails. Invoked asynchronously on the main thread in the future.
  677. @objc(sendEmailVerificationWithCompletion:)
  678. open func __sendEmailVerification(withCompletion completion: ((Error?) -> Void)?) {
  679. sendEmailVerification(completion: completion)
  680. }
  681. /// Initiates email verification for the user.
  682. ///
  683. /// Possible error codes:
  684. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  685. /// sent in the request.
  686. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  687. /// the console for this action.
  688. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  689. /// sending update email.
  690. /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
  691. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  692. /// handling action codes.
  693. /// - Parameter completion: Optionally; the block invoked when the request to send an email
  694. /// verification is complete, or fails. Invoked asynchronously on the main thread in the future.
  695. @objc(sendEmailVerificationWithActionCodeSettings:completion:)
  696. open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil,
  697. completion: ((Error?) -> Void)? = nil) {
  698. Task {
  699. do {
  700. try await sendEmailVerification(with: actionCodeSettings)
  701. await MainActor.run {
  702. completion?(nil)
  703. }
  704. } catch {
  705. await MainActor.run {
  706. completion?(error)
  707. }
  708. }
  709. }
  710. }
  711. /// Initiates email verification for the user.
  712. ///
  713. /// Possible error codes:
  714. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  715. /// sent in the request.
  716. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  717. /// the console for this action.
  718. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  719. /// sending update email.
  720. /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
  721. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  722. /// handling action codes. The default value is `nil`.
  723. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  724. open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil) async throws {
  725. return try await auth.authWorker.sendEmailVerification(user: self, with: actionCodeSettings)
  726. }
  727. /// Deletes the user account (also signs out the user, if this was the current user).
  728. ///
  729. /// Possible error codes:
  730. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  731. /// operation that requires a recent login from the user. This error indicates the user
  732. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  733. /// `reauthenticate(with:)`.
  734. /// - Parameter completion: Optionally; the block invoked when the request to delete the account
  735. /// is complete, or fails. Invoked asynchronously on the main thread in the future.
  736. @objc open func delete(completion: ((Error?) -> Void)? = nil) {
  737. Task {
  738. do {
  739. try await delete()
  740. await MainActor.run {
  741. completion?(nil)
  742. }
  743. } catch {
  744. await MainActor.run {
  745. completion?(error)
  746. }
  747. }
  748. }
  749. }
  750. /// Deletes the user account (also signs out the user, if this was the current user).
  751. ///
  752. /// Possible error codes:
  753. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  754. /// operation that requires a recent login from the user. This error indicates the user
  755. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  756. /// `reauthenticate(with:)`.
  757. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  758. open func delete() async throws {
  759. return try await auth.authWorker.delete(user: self)
  760. }
  761. /// Send an email to verify the ownership of the account then update to the new email.
  762. /// - Parameter email: The email to be updated to.
  763. /// - Parameter completion: Optionally; the block invoked when the request to send the
  764. /// verification email is complete, or fails.
  765. @objc(sendEmailVerificationBeforeUpdatingEmail:completion:)
  766. open func __sendEmailVerificationBeforeUpdating(email: String, completion: ((Error?) -> Void)?) {
  767. sendEmailVerification(beforeUpdatingEmail: email, completion: completion)
  768. }
  769. /// Send an email to verify the ownership of the account then update to the new email.
  770. /// - Parameter email: The email to be updated to.
  771. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  772. /// handling action codes.
  773. /// - Parameter completion: Optionally; the block invoked when the request to send the
  774. /// verification email is complete, or fails.
  775. @objc open func sendEmailVerification(beforeUpdatingEmail email: String,
  776. actionCodeSettings: ActionCodeSettings? = nil,
  777. completion: ((Error?) -> Void)? = nil) {
  778. Task {
  779. do {
  780. try await sendEmailVerification(beforeUpdatingEmail: email,
  781. actionCodeSettings: actionCodeSettings)
  782. await MainActor.run {
  783. completion?(nil)
  784. }
  785. } catch {
  786. await MainActor.run {
  787. completion?(error)
  788. }
  789. }
  790. }
  791. }
  792. /// Send an email to verify the ownership of the account then update to the new email.
  793. /// - Parameter email: The email to be updated to.
  794. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  795. /// handling action codes.
  796. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  797. open func sendEmailVerification(beforeUpdatingEmail newEmail: String,
  798. actionCodeSettings: ActionCodeSettings? = nil) async throws {
  799. return try await auth.authWorker.sendEmailVerification(
  800. user: self,
  801. beforeUpdatingEmail: newEmail,
  802. actionCodeSettings: actionCodeSettings
  803. )
  804. }
  805. // MARK: Internal implementations below
  806. func rawAccessToken() -> String {
  807. return tokenService.accessToken
  808. }
  809. func accessTokenExpirationDate() -> Date? {
  810. return tokenService.accessTokenExpirationDate
  811. }
  812. init(withTokenService tokenService: SecureTokenService) {
  813. providerDataRaw = [:]
  814. taskQueue = AuthSerialTaskQueue()
  815. self.tokenService = tokenService
  816. isAnonymous = false
  817. isEmailVerified = false
  818. metadata = UserMetadata(withCreationDate: nil, lastSignInDate: nil)
  819. tenantID = nil
  820. #if os(iOS)
  821. multiFactor = MultiFactor(withMFAEnrollments: [])
  822. #endif
  823. uid = ""
  824. hasEmailPasswordCredential = false
  825. requestConfiguration = AuthRequestConfiguration(apiKey: "", appID: "")
  826. }
  827. class func retrieveUser(withAuth auth: Auth,
  828. accessToken: String?,
  829. accessTokenExpirationDate: Date?,
  830. refreshToken: String?,
  831. anonymous: Bool) async throws -> User {
  832. guard let accessToken = accessToken,
  833. let refreshToken = refreshToken else {
  834. fatalError("Internal FirebaseAuth Error: nil token")
  835. }
  836. let tokenService = SecureTokenService(withRequestConfiguration: auth.requestConfiguration,
  837. accessToken: accessToken,
  838. accessTokenExpirationDate: accessTokenExpirationDate,
  839. refreshToken: refreshToken)
  840. let user = User(withTokenService: tokenService)
  841. user.auth = auth
  842. user.tenantID = auth.tenantID
  843. user.requestConfiguration = auth.requestConfiguration
  844. let accessToken2 = try await user.internalGetToken()
  845. let getAccountInfoRequest = GetAccountInfoRequest(
  846. accessToken: accessToken2,
  847. requestConfiguration: user.requestConfiguration
  848. )
  849. let response = try await AuthBackend.call(with: getAccountInfoRequest)
  850. user.isAnonymous = anonymous
  851. user.update(withGetAccountInfoResponse: response)
  852. return user
  853. }
  854. @objc open var providerID: String {
  855. return "Firebase"
  856. }
  857. /// The provider's user ID for the user.
  858. @objc open var uid: String
  859. /// The name of the user.
  860. @objc open var displayName: String?
  861. /// The URL of the user's profile photo.
  862. @objc open var photoURL: URL?
  863. /// The user's email address.
  864. @objc open var email: String?
  865. /// A phone number associated with the user.
  866. ///
  867. /// This property is only available for users authenticated via phone number auth.
  868. @objc open var phoneNumber: String?
  869. /// Whether or not the user can be authenticated by using Firebase email and password.
  870. var hasEmailPasswordCredential: Bool
  871. /// Used to serialize the update profile calls.
  872. private var taskQueue: AuthSerialTaskQueue
  873. /// A strong reference to a requestConfiguration instance associated with this user instance.
  874. var requestConfiguration: AuthRequestConfiguration
  875. /// A secure token service associated with this user. For performing token exchanges and
  876. /// refreshing access tokens.
  877. var tokenService: SecureTokenService
  878. private weak var _auth: Auth?
  879. /// A weak reference to an `Auth` instance associated with this instance.
  880. weak var auth: Auth! {
  881. set {
  882. _auth = newValue
  883. guard let requestConfiguration = auth?.requestConfiguration else {
  884. fatalError("Firebase Auth Internal Error: nil requestConfiguration when initializing User")
  885. }
  886. tokenService.requestConfiguration = requestConfiguration
  887. self.requestConfiguration = requestConfiguration
  888. }
  889. get { return _auth }
  890. }
  891. // MARK: Private functions
  892. /// Sets a new token service for the `User` instance.
  893. ///
  894. /// The method makes sure the token service has access and refresh token and the new tokens
  895. /// are saved in the keychain before calling back.
  896. /// - Parameter tokenService: The new token service object.
  897. /// - Parameter callback: The block to be called in the global auth working queue once finished.
  898. func setTokenService(tokenService: SecureTokenService,
  899. callback: @escaping (Error?) -> Void) {
  900. tokenService.fetchAccessToken(forcingRefresh: false) { token, error, tokenUpdated in
  901. if let error {
  902. callback(error)
  903. return
  904. }
  905. self.tokenService = tokenService
  906. if let error = self.updateKeychain() {
  907. callback(error)
  908. return
  909. }
  910. callback(nil)
  911. }
  912. }
  913. /// Sets a new token service for the `User` instance.
  914. ///
  915. /// The method makes sure the token service has access and refresh token and the new tokens
  916. /// are saved in the keychain before calling back.
  917. /// - Parameter tokenService: The new token service object.
  918. func setTokenService(tokenService: SecureTokenService) async throws {
  919. self.tokenService = tokenService
  920. if let error = updateKeychain() {
  921. throw error
  922. }
  923. }
  924. func update(withGetAccountInfoResponse response: GetAccountInfoResponse) {
  925. guard let user = response.users?.first else {
  926. // Silent fallthrough in ObjC code.
  927. AuthLog.logWarning(code: "I-AUT000016", message: "Missing user in GetAccountInfoResponse")
  928. return
  929. }
  930. uid = user.localID ?? ""
  931. email = user.email
  932. isEmailVerified = user.emailVerified
  933. displayName = user.displayName
  934. photoURL = user.photoURL
  935. phoneNumber = user.phoneNumber
  936. hasEmailPasswordCredential = user.passwordHash != nil && user.passwordHash!.count > 0
  937. metadata = UserMetadata(withCreationDate: user.creationDate,
  938. lastSignInDate: user.lastLoginDate)
  939. var providerData: [String: UserInfoImpl] = [:]
  940. if let providerUserInfos = user.providerUserInfo {
  941. for providerUserInfo in providerUserInfos {
  942. let userInfo = UserInfoImpl.userInfo(
  943. withGetAccountInfoResponseProviderUserInfo: providerUserInfo
  944. )
  945. if let providerID = providerUserInfo.providerID {
  946. providerData[providerID] = userInfo
  947. }
  948. }
  949. }
  950. providerDataRaw = providerData
  951. #if os(iOS)
  952. if let enrollments = user.mfaEnrollments {
  953. multiFactor = MultiFactor(withMFAEnrollments: enrollments)
  954. }
  955. multiFactor.user = self
  956. #endif
  957. }
  958. /// Signs out this user if the user or the token is invalid.
  959. /// - Parameter error: The error from the server.
  960. func signOutIfTokenIsInvalid(withError error: Error) {
  961. let code = (error as NSError).code
  962. if code == AuthErrorCode.userNotFound.rawValue ||
  963. code == AuthErrorCode.userDisabled.rawValue ||
  964. code == AuthErrorCode.invalidUserToken.rawValue ||
  965. code == AuthErrorCode.userTokenExpired.rawValue {
  966. AuthLog.logNotice(code: "I-AUT000016",
  967. message: "Invalid user token detected, user is automatically signed out.")
  968. try? auth?.signOutByForce(withUserID: uid)
  969. }
  970. }
  971. func internalGetToken(forceRefresh: Bool = false) async throws -> String {
  972. do {
  973. let (token, tokenUpdated) = try await tokenService.fetchAccessToken(
  974. user: self,
  975. forcingRefresh: forceRefresh
  976. )
  977. if tokenUpdated {
  978. if let error = updateKeychain() {
  979. throw error
  980. }
  981. }
  982. return token!
  983. } catch {
  984. signOutIfTokenIsInvalid(withError: error)
  985. throw error
  986. }
  987. }
  988. /// Updates the keychain for user token or info changes.
  989. /// - Returns: An `Error` on failure.
  990. func updateKeychain() -> Error? {
  991. return auth?.updateKeychain(withUser: self)
  992. }
  993. // MARK: NSSecureCoding
  994. private let kUserIDCodingKey = "userID"
  995. private let kHasEmailPasswordCredentialCodingKey = "hasEmailPassword"
  996. private let kAnonymousCodingKey = "anonymous"
  997. private let kEmailCodingKey = "email"
  998. private let kPhoneNumberCodingKey = "phoneNumber"
  999. private let kEmailVerifiedCodingKey = "emailVerified"
  1000. private let kDisplayNameCodingKey = "displayName"
  1001. private let kPhotoURLCodingKey = "photoURL"
  1002. private let kProviderDataKey = "providerData"
  1003. private let kAPIKeyCodingKey = "APIKey"
  1004. private let kFirebaseAppIDCodingKey = "firebaseAppID"
  1005. private let kTokenServiceCodingKey = "tokenService"
  1006. private let kMetadataCodingKey = "metadata"
  1007. private let kMultiFactorCodingKey = "multiFactor"
  1008. private let kTenantIDCodingKey = "tenantID"
  1009. public static var supportsSecureCoding: Bool {
  1010. return true
  1011. }
  1012. public func encode(with coder: NSCoder) {
  1013. coder.encode(uid, forKey: kUserIDCodingKey)
  1014. coder.encode(isAnonymous, forKey: kAnonymousCodingKey)
  1015. coder.encode(hasEmailPasswordCredential, forKey: kHasEmailPasswordCredentialCodingKey)
  1016. coder.encode(providerDataRaw, forKey: kProviderDataKey)
  1017. coder.encode(email, forKey: kEmailCodingKey)
  1018. coder.encode(phoneNumber, forKey: kPhoneNumberCodingKey)
  1019. coder.encode(isEmailVerified, forKey: kEmailVerifiedCodingKey)
  1020. coder.encode(photoURL, forKey: kPhotoURLCodingKey)
  1021. coder.encode(displayName, forKey: kDisplayNameCodingKey)
  1022. coder.encode(metadata, forKey: kMetadataCodingKey)
  1023. coder.encode(tenantID, forKey: kTenantIDCodingKey)
  1024. if let auth {
  1025. coder.encode(auth.requestConfiguration.apiKey, forKey: kAPIKeyCodingKey)
  1026. coder.encode(auth.requestConfiguration.appID, forKey: kFirebaseAppIDCodingKey)
  1027. }
  1028. coder.encode(tokenService, forKey: kTokenServiceCodingKey)
  1029. #if os(iOS)
  1030. coder.encode(multiFactor, forKey: kMultiFactorCodingKey)
  1031. #endif
  1032. }
  1033. public required init?(coder: NSCoder) {
  1034. guard let userID = coder.decodeObject(of: NSString.self, forKey: kUserIDCodingKey) as? String,
  1035. let apiKey = coder.decodeObject(of: NSString.self, forKey: kAPIKeyCodingKey) as? String,
  1036. let appID = coder.decodeObject(
  1037. of: NSString.self,
  1038. forKey: kFirebaseAppIDCodingKey
  1039. ) as? String,
  1040. let tokenService = coder.decodeObject(of: SecureTokenService.self,
  1041. forKey: kTokenServiceCodingKey) else {
  1042. return nil
  1043. }
  1044. let anonymous = coder.decodeBool(forKey: kAnonymousCodingKey)
  1045. let hasEmailPasswordCredential = coder.decodeBool(forKey: kHasEmailPasswordCredentialCodingKey)
  1046. let displayName = coder.decodeObject(
  1047. of: NSString.self,
  1048. forKey: kDisplayNameCodingKey
  1049. ) as? String
  1050. let photoURL = coder.decodeObject(of: NSURL.self, forKey: kPhotoURLCodingKey) as? URL
  1051. let email = coder.decodeObject(of: NSString.self, forKey: kEmailCodingKey) as? String
  1052. let phoneNumber = coder.decodeObject(
  1053. of: NSString.self,
  1054. forKey: kPhoneNumberCodingKey
  1055. ) as? String
  1056. let emailVerified = coder.decodeBool(forKey: kEmailVerifiedCodingKey)
  1057. let classes = [NSDictionary.self, NSString.self, UserInfoImpl.self]
  1058. let providerData = coder.decodeObject(of: classes, forKey: kProviderDataKey)
  1059. as? [String: UserInfoImpl]
  1060. let metadata = coder.decodeObject(of: UserMetadata.self, forKey: kMetadataCodingKey)
  1061. let tenantID = coder.decodeObject(of: NSString.self, forKey: kTenantIDCodingKey) as? String
  1062. #if os(iOS)
  1063. let multiFactor = coder.decodeObject(of: MultiFactor.self, forKey: kMultiFactorCodingKey)
  1064. #endif
  1065. self.tokenService = tokenService
  1066. uid = userID
  1067. isAnonymous = anonymous
  1068. self.hasEmailPasswordCredential = hasEmailPasswordCredential
  1069. self.email = email
  1070. isEmailVerified = emailVerified
  1071. self.displayName = displayName
  1072. self.photoURL = photoURL
  1073. providerDataRaw = providerData ?? [:]
  1074. self.phoneNumber = phoneNumber
  1075. self.metadata = metadata ?? UserMetadata(withCreationDate: nil, lastSignInDate: nil)
  1076. self.tenantID = tenantID
  1077. // The `heartbeatLogger` and `appCheck` will be set later via a property update.
  1078. requestConfiguration = AuthRequestConfiguration(apiKey: apiKey, appID: appID)
  1079. taskQueue = AuthSerialTaskQueue()
  1080. #if os(iOS)
  1081. self.multiFactor = multiFactor ?? MultiFactor()
  1082. super.init()
  1083. multiFactor?.user = self
  1084. #endif
  1085. }
  1086. }