User.swift 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917
  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. #if os(iOS) || os(tvOS) || os(macOS) || targetEnvironment(macCatalyst)
  16. import AuthenticationServices
  17. #endif
  18. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  19. extension User: NSSecureCoding {}
  20. /// Represents a user.
  21. ///
  22. /// Firebase Auth does not attempt to validate users
  23. /// when loading them from the keychain. Invalidated users (such as those
  24. /// whose passwords have been changed on another client) are automatically
  25. /// logged out when an auth-dependent operation is attempted or when the
  26. /// ID token is automatically refreshed.
  27. ///
  28. /// This class is thread-safe.
  29. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  30. @objc(FIRUser) open class User: NSObject, UserInfo {
  31. /// Indicates the user represents an anonymous user.
  32. @objc public internal(set) var isAnonymous: Bool
  33. /// Indicates the user represents an anonymous user.
  34. @objc open func anonymous() -> Bool { return isAnonymous }
  35. /// Indicates the email address associated with this user has been verified.
  36. @objc public private(set) var isEmailVerified: Bool
  37. /// Indicates the email address associated with this user has been verified.
  38. @objc open func emailVerified() -> Bool { return isEmailVerified }
  39. /// Profile data for each identity provider, if any.
  40. ///
  41. /// This data is cached on sign-in and updated when linking or unlinking.
  42. @objc open var providerData: [UserInfo] {
  43. return Array(providerDataRaw.values)
  44. }
  45. var providerDataRaw: [String: UserInfoImpl]
  46. /// The backend service for the given instance.
  47. private(set) var backend: AuthBackend
  48. /// Metadata associated with the Firebase user in question.
  49. @objc public private(set) var metadata: UserMetadata
  50. /// The tenant ID of the current user. `nil` if none is available.
  51. @objc public private(set) var tenantID: String?
  52. #if os(iOS)
  53. /// Multi factor object associated with the user.
  54. ///
  55. /// This property is available on iOS only.
  56. @objc public private(set) var multiFactor: MultiFactor
  57. public private(set) var enrolledPasskeys: [PasskeyInfo]?
  58. #endif
  59. /// [Deprecated] Updates the email address for the user.
  60. ///
  61. /// On success, the cached user profile data is updated. Returns an error when
  62. /// [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  63. /// is enabled.
  64. ///
  65. /// May fail if there is already an account with this email address that was created using
  66. /// email and password authentication.
  67. ///
  68. /// Invoked asynchronously on the main thread in the future.
  69. ///
  70. /// Possible error codes:
  71. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  72. /// sent in the request.
  73. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  74. /// the console for this action.
  75. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  76. /// sending update email.
  77. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another
  78. /// account.
  79. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  80. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security
  81. /// sensitive operation that requires a recent login from the user. This error indicates
  82. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  83. /// calling `reauthenticate(with:)`.
  84. /// - Parameter email: The email address for the user.
  85. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  86. /// finished.
  87. #if !FIREBASE_CI
  88. @available(
  89. *,
  90. deprecated,
  91. message: "`updateEmail` is deprecated and will be removed in a future release. Use sendEmailVerification(beforeUpdatingEmail:) instead."
  92. )
  93. #endif // !FIREBASE_CI
  94. @objc(updateEmail:completion:)
  95. open func updateEmail(to email: String, completion: ((Error?) -> Void)? = nil) {
  96. kAuthGlobalWorkQueue.async {
  97. self.updateEmail(email: email, password: nil) { error in
  98. User.callInMainThreadWithError(callback: completion, error: error)
  99. }
  100. }
  101. }
  102. /// [Deprecated] Updates the email address for the user.
  103. ///
  104. /// On success, the cached user profile data is updated. Throws when
  105. /// [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)
  106. /// is enabled.
  107. ///
  108. /// May fail if there is already an account with this email address that was created using
  109. /// email and password authentication.
  110. ///
  111. /// Invoked asynchronously on the main thread in the future.
  112. ///
  113. /// Possible error codes:
  114. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  115. /// sent in the request.
  116. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  117. /// the console for this action.
  118. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  119. /// sending update email.
  120. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another
  121. /// account.
  122. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  123. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security
  124. /// sensitive operation that requires a recent login from the user. This error indicates
  125. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  126. /// calling `reauthenticate(with:)`.
  127. /// - Parameter email: The email address for the user.
  128. #if !FIREBASE_CI
  129. @available(
  130. *,
  131. deprecated,
  132. message: "`updateEmail` is deprecated and will be removed in a future release. Use sendEmailVerification(beforeUpdatingEmail:) instead."
  133. )
  134. #endif // !FIREBASE_CI
  135. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  136. open func updateEmail(to email: String) async throws {
  137. return try await withCheckedThrowingContinuation { continuation in
  138. self.updateEmail(to: email) { error in
  139. if let error {
  140. continuation.resume(throwing: error)
  141. } else {
  142. continuation.resume()
  143. }
  144. }
  145. }
  146. }
  147. /// Updates the password for the user. On success, the cached user profile data is updated.
  148. ///
  149. /// Invoked asynchronously on the main thread in the future.
  150. ///
  151. /// Possible error codes:
  152. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled
  153. /// sign in with the specified identity provider.
  154. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security
  155. /// sensitive operation that requires a recent login from the user. This error indicates
  156. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  157. /// calling `reauthenticate(with:)`.
  158. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  159. /// considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  160. /// dictionary object will contain more detailed explanation that can be shown to the user.
  161. /// - Parameter password: The new password for the user.
  162. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  163. /// finished.
  164. @objc(updatePassword:completion:)
  165. open func updatePassword(to password: String, completion: ((Error?) -> Void)? = nil) {
  166. guard password.count > 0 else {
  167. if let completion {
  168. completion(AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing Password"))
  169. }
  170. return
  171. }
  172. kAuthGlobalWorkQueue.async {
  173. self.updateEmail(email: nil, password: password) { error in
  174. User.callInMainThreadWithError(callback: completion, error: error)
  175. }
  176. }
  177. }
  178. /// Updates the password for the user. On success, the cached user profile data is updated.
  179. ///
  180. /// Invoked asynchronously on the main thread in the future.
  181. ///
  182. /// Possible error codes:
  183. /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled
  184. /// sign in with the specified identity provider.
  185. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security
  186. /// sensitive operation that requires a recent login from the user. This error indicates
  187. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  188. /// calling `reauthenticate(with:)`.
  189. /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
  190. /// considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
  191. /// dictionary object will contain more detailed explanation that can be shown to the user.
  192. /// - Parameter password: The new password for the user.
  193. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  194. open func updatePassword(to password: String) async throws {
  195. return try await withCheckedThrowingContinuation { continuation in
  196. self.updatePassword(to: password) { error in
  197. if let error {
  198. continuation.resume(throwing: error)
  199. } else {
  200. continuation.resume()
  201. }
  202. }
  203. }
  204. }
  205. #if os(iOS)
  206. /// Updates the phone number for the user. On success, the cached user profile data is updated.
  207. ///
  208. /// Invoked asynchronously on the main thread in the future.
  209. ///
  210. /// This method is available on iOS only.
  211. ///
  212. /// Possible error codes:
  213. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security
  214. /// sensitive operation that requires a recent login from the user. This error indicates
  215. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  216. /// calling `reauthenticate(with:)`.
  217. /// - Parameter credential: The new phone number credential corresponding to the
  218. /// phone number to be added to the Firebase account, if a phone number is already linked to the
  219. /// account this new phone number will replace it.
  220. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  221. /// finished.
  222. @objc(updatePhoneNumberCredential:completion:)
  223. open func updatePhoneNumber(_ credential: PhoneAuthCredential,
  224. completion: ((Error?) -> Void)? = nil) {
  225. kAuthGlobalWorkQueue.async {
  226. self.internalUpdateOrLinkPhoneNumber(credential: credential,
  227. isLinkOperation: false) { error in
  228. User.callInMainThreadWithError(callback: completion, error: error)
  229. }
  230. }
  231. }
  232. /// Updates the phone number for the user. On success, the cached user profile data is updated.
  233. ///
  234. /// Invoked asynchronously on the main thread in the future.
  235. ///
  236. /// This method is available on iOS only.
  237. ///
  238. /// Possible error codes:
  239. /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security
  240. /// sensitive operation that requires a recent login from the user. This error indicates
  241. /// the user has not signed in recently enough. To resolve, reauthenticate the user by
  242. /// calling `reauthenticate(with:)`.
  243. /// - Parameter credential: The new phone number credential corresponding to the
  244. /// phone number to be added to the Firebase account, if a phone number is already linked to the
  245. /// account this new phone number will replace it.
  246. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  247. open func updatePhoneNumber(_ credential: PhoneAuthCredential) async throws {
  248. return try await withCheckedThrowingContinuation { continuation in
  249. self.updatePhoneNumber(credential) { error in
  250. if let error {
  251. continuation.resume(throwing: error)
  252. } else {
  253. continuation.resume()
  254. }
  255. }
  256. }
  257. }
  258. #endif
  259. /// Creates an object which may be used to change the user's profile data.
  260. ///
  261. /// Set the properties of the returned object, then call
  262. /// `UserProfileChangeRequest.commitChanges()` to perform the updates atomically.
  263. /// - Returns: An object which may be used to change the user's profile data atomically.
  264. @objc(profileChangeRequest)
  265. open func createProfileChangeRequest() -> UserProfileChangeRequest {
  266. var result: UserProfileChangeRequest!
  267. kAuthGlobalWorkQueue.sync {
  268. result = UserProfileChangeRequest(self)
  269. }
  270. return result
  271. }
  272. /// A refresh token; useful for obtaining new access tokens independently.
  273. ///
  274. /// This property should only be used for advanced scenarios, and is not typically needed.
  275. @objc open var refreshToken: String? {
  276. var result: String?
  277. kAuthGlobalWorkQueue.sync {
  278. result = self.tokenService.refreshToken
  279. }
  280. return result
  281. }
  282. /// Reloads the user's profile data from the server.
  283. ///
  284. /// May fail with an `AuthErrorCodeRequiresRecentLogin` error code. In this case
  285. /// you should call `reauthenticate(with:)` before re-invoking
  286. /// `updateEmail(to:)`.
  287. /// - Parameter completion: Optionally; the block invoked when the reload has finished. Invoked
  288. /// asynchronously on the main thread in the future.
  289. @objc open func reload(completion: ((Error?) -> Void)? = nil) {
  290. kAuthGlobalWorkQueue.async {
  291. self.getAccountInfoRefreshingCache { user, error in
  292. User.callInMainThreadWithError(callback: completion, error: error)
  293. }
  294. }
  295. }
  296. /// Reloads the user's profile data from the server.
  297. ///
  298. /// May fail with an `AuthErrorCodeRequiresRecentLogin` error code. In this case
  299. /// you should call `reauthenticate(with:)` before re-invoking
  300. /// `updateEmail(to:)`.
  301. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  302. open func reload() async throws {
  303. return try await withCheckedThrowingContinuation { continuation in
  304. self.reload { error in
  305. if let error {
  306. continuation.resume(throwing: error)
  307. } else {
  308. continuation.resume()
  309. }
  310. }
  311. }
  312. }
  313. /// Renews the user's authentication tokens by validating a fresh set of credentials supplied
  314. /// by the user and returns additional identity provider data.
  315. ///
  316. /// If the user associated with the supplied credential is different from the current user,
  317. /// or if the validation of the supplied credentials fails; an error is returned and the current
  318. /// user remains signed in.
  319. ///
  320. /// Possible error codes:
  321. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  322. /// This could happen if it has expired or it is malformed.
  323. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the
  324. /// identity provider represented by the credential are not enabled. Enable them in the
  325. /// Auth section of the Firebase console.
  326. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential
  327. /// (e.g. the email in a Facebook access token) is already in use by an existing account,
  328. /// that cannot be authenticated with this method. This error will only be thrown if the
  329. /// "One account per email address" setting is enabled in the Firebase console, under Auth
  330. /// settings. Please note that the error code raised in this specific situation may not be
  331. /// the same on Web and Android.
  332. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  333. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with
  334. /// an incorrect password, if credential is of the type `EmailPasswordAuthCredential`.
  335. /// * `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to
  336. /// reauthenticate with a user which is not the current user.
  337. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  338. /// - Parameter credential: A user-supplied credential, which will be validated by the server.
  339. /// This can be a successful third-party identity provider sign-in, or an email address and
  340. /// password.
  341. /// - Parameter completion: Optionally; the block invoked when the re-authentication operation has
  342. /// finished. Invoked asynchronously on the main thread in the future.
  343. @objc(reauthenticateWithCredential:completion:)
  344. open func reauthenticate(with credential: AuthCredential,
  345. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  346. kAuthGlobalWorkQueue.async {
  347. Task {
  348. do {
  349. let authResult = try await self.auth?.internalSignInAndRetrieveData(
  350. withCredential: credential,
  351. isReauthentication: true
  352. )
  353. guard let user = authResult?.user,
  354. user.uid == self.auth?.getUserID() else {
  355. User.callInMainThreadWithAuthDataResultAndError(
  356. callback: completion,
  357. result: authResult,
  358. error: AuthErrorUtils.userMismatchError()
  359. )
  360. return
  361. }
  362. // Successful reauthenticate
  363. do {
  364. try await self.userProfileUpdate.setTokenService(user: self,
  365. tokenService: user.tokenService)
  366. User.callInMainThreadWithAuthDataResultAndError(callback: completion,
  367. result: authResult,
  368. error: nil)
  369. } catch {
  370. User.callInMainThreadWithAuthDataResultAndError(callback: completion,
  371. result: authResult,
  372. error: error)
  373. }
  374. } catch {
  375. // If "user not found" error returned by backend,
  376. // translate to user mismatch error which is more
  377. // accurate.
  378. var reportError: Error = error
  379. if (error as NSError).code == AuthErrorCode.userNotFound.rawValue {
  380. reportError = AuthErrorUtils.userMismatchError()
  381. }
  382. User.callInMainThreadWithAuthDataResultAndError(callback: completion,
  383. result: nil,
  384. error: reportError)
  385. }
  386. }
  387. }
  388. }
  389. /// Renews the user's authentication tokens by validating a fresh set of credentials supplied
  390. /// by the user and returns additional identity provider data.
  391. ///
  392. /// If the user associated with the supplied credential is different from the current user,
  393. /// or if the validation of the supplied credentials fails; an error is returned and the current
  394. /// user remains signed in.
  395. ///
  396. /// Possible error codes:
  397. /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
  398. /// This could happen if it has expired or it is malformed.
  399. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the
  400. /// identity provider represented by the credential are not enabled. Enable them in the
  401. /// Auth section of the Firebase console.
  402. /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential
  403. /// (e.g. the email in a Facebook access token) is already in use by an existing account,
  404. /// that cannot be authenticated with this method. This error will only be thrown if the
  405. /// "One account per email address" setting is enabled in the Firebase console, under Auth
  406. /// settings. Please note that the error code raised in this specific situation may not be
  407. /// the same on Web and Android.
  408. /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
  409. /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with
  410. /// an incorrect password, if credential is of the type `EmailPasswordAuthCredential`.
  411. /// * `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to
  412. /// reauthenticate with a user which is not the current user.
  413. /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
  414. /// - Parameter credential: A user-supplied credential, which will be validated by the server.
  415. /// This can be a successful third-party identity provider sign-in, or an email address and
  416. /// password.
  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 credential: AuthCredential) async throws -> AuthDataResult {
  421. return try await withCheckedThrowingContinuation { continuation in
  422. self.reauthenticate(with: credential) { result, error in
  423. if let result {
  424. continuation.resume(returning: result)
  425. } else if let error {
  426. continuation.resume(throwing: error)
  427. }
  428. }
  429. }
  430. }
  431. #if os(iOS)
  432. /// Renews the user's authentication using the provided auth provider instance.
  433. ///
  434. /// This method is available on iOS only.
  435. /// - Parameter provider: An instance of an auth provider used to initiate the reauthenticate
  436. /// flow.
  437. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  438. /// protocol, used for presenting the web context. If nil, a default `AuthUIDelegate`
  439. /// will be used.
  440. /// - Parameter completion: Optionally; a block which is invoked when the reauthenticate flow
  441. /// finishes, or is canceled. Invoked asynchronously on the main thread in the future.
  442. @objc(reauthenticateWithProvider:UIDelegate:completion:)
  443. open func reauthenticate(with provider: FederatedAuthProvider,
  444. uiDelegate: AuthUIDelegate?,
  445. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  446. kAuthGlobalWorkQueue.async {
  447. Task {
  448. do {
  449. let credential = try await provider.credential(with: uiDelegate)
  450. self.reauthenticate(with: credential, completion: completion)
  451. } catch {
  452. if let completion {
  453. completion(nil, error)
  454. }
  455. }
  456. }
  457. }
  458. }
  459. /// Renews the user's authentication using the provided auth provider instance.
  460. ///
  461. /// This method is available on iOS only.
  462. /// - Parameter provider: An instance of an auth provider used to initiate the reauthenticate
  463. /// flow.
  464. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  465. /// protocol, used for presenting the web context. If nil, a default `AuthUIDelegate`
  466. /// will be used.
  467. /// - Returns: The `AuthDataResult` after the reauthentication.
  468. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  469. @discardableResult
  470. open func reauthenticate(with provider: FederatedAuthProvider,
  471. uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
  472. return try await withCheckedThrowingContinuation { continuation in
  473. self.reauthenticate(with: provider, uiDelegate: uiDelegate) { result, error in
  474. if let result {
  475. continuation.resume(returning: result)
  476. } else if let error {
  477. continuation.resume(throwing: error)
  478. }
  479. }
  480. }
  481. }
  482. #endif
  483. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  484. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  485. /// asynchronously on the main thread in the future.
  486. @objc(getIDTokenWithCompletion:)
  487. open func getIDToken(completion: ((String?, Error?) -> Void)?) {
  488. // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to
  489. // global work queue here.
  490. getIDTokenForcingRefresh(false, completion: completion)
  491. }
  492. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  493. ///
  494. /// The authentication token will be refreshed (by making a network request) if it has
  495. /// expired, or if `forceRefresh` is `true`.
  496. /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some
  497. /// reason other than an expiration.
  498. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  499. /// asynchronously on the main thread in the future.
  500. @objc(getIDTokenForcingRefresh:completion:)
  501. open func getIDTokenForcingRefresh(_ forceRefresh: Bool,
  502. completion: ((String?, Error?) -> Void)?) {
  503. getIDTokenResult(forcingRefresh: forceRefresh) { tokenResult, error in
  504. if let completion {
  505. DispatchQueue.main.async {
  506. completion(tokenResult?.token, error)
  507. }
  508. }
  509. }
  510. }
  511. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  512. ///
  513. /// The authentication token will be refreshed (by making a network request) if it has
  514. /// expired, or if `forceRefresh` is `true`.
  515. /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some
  516. /// reason other than an expiration.
  517. /// - Returns: The Firebase authentication token.
  518. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  519. open func getIDToken(forcingRefresh forceRefresh: Bool = false) async throws -> String {
  520. return try await withCheckedThrowingContinuation { continuation in
  521. self.getIDTokenForcingRefresh(forceRefresh) { tokenResult, error in
  522. if let tokenResult {
  523. continuation.resume(returning: tokenResult)
  524. } else if let error {
  525. continuation.resume(throwing: error)
  526. }
  527. }
  528. }
  529. }
  530. /// API included for compatibility with a mis-named Firebase 10 API.
  531. /// Use `getIDToken(forcingRefresh forceRefresh: Bool = false)` instead.
  532. open func idTokenForcingRefresh(_ forceRefresh: Bool) async throws -> String {
  533. return try await getIDToken(forcingRefresh: forceRefresh)
  534. }
  535. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  536. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  537. /// asynchronously on the main thread in the future.
  538. @objc(getIDTokenResultWithCompletion:)
  539. open func getIDTokenResult(completion: ((AuthTokenResult?, Error?) -> Void)?) {
  540. getIDTokenResult(forcingRefresh: false) { tokenResult, error in
  541. if let completion {
  542. DispatchQueue.main.async {
  543. completion(tokenResult, error)
  544. }
  545. }
  546. }
  547. }
  548. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  549. ///
  550. /// The authentication token will be refreshed (by making a network request) if it has
  551. /// expired, or if `forcingRefresh` is `true`.
  552. /// - Parameter forcingRefresh: Forces a token refresh. Useful if the token becomes invalid for
  553. /// some
  554. /// reason other than an expiration.
  555. /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked
  556. /// asynchronously on the main thread in the future.
  557. @objc(getIDTokenResultForcingRefresh:completion:)
  558. open func getIDTokenResult(forcingRefresh: Bool,
  559. completion: ((AuthTokenResult?, Error?) -> Void)?) {
  560. kAuthGlobalWorkQueue.async {
  561. self.internalGetToken(forceRefresh: forcingRefresh, backend: self.backend) { token, error in
  562. var tokenResult: AuthTokenResult?
  563. if let token {
  564. do {
  565. tokenResult = try AuthTokenResult.tokenResult(token: token)
  566. AuthLog.logDebug(code: "I-AUT000017", message: "Actual token expiration date: " +
  567. "\(String(describing: tokenResult?.expirationDate))," +
  568. "current date: \(Date())")
  569. if let completion {
  570. DispatchQueue.main.async {
  571. completion(tokenResult, error)
  572. }
  573. }
  574. return
  575. } catch {
  576. if let completion {
  577. DispatchQueue.main.async {
  578. completion(tokenResult, error)
  579. }
  580. }
  581. return
  582. }
  583. }
  584. if let completion {
  585. DispatchQueue.main.async {
  586. completion(nil, error)
  587. }
  588. }
  589. }
  590. }
  591. }
  592. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  593. ///
  594. /// The authentication token will be refreshed (by making a network request) if it has
  595. /// expired, or if `forceRefresh` is `true`.
  596. /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some
  597. /// reason other than an expiration.
  598. /// - Returns: The Firebase authentication token.
  599. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  600. open func getIDTokenResult(forcingRefresh forceRefresh: Bool = false) async throws
  601. -> AuthTokenResult {
  602. return try await withCheckedThrowingContinuation { continuation in
  603. self.getIDTokenResult(forcingRefresh: forceRefresh) { tokenResult, error in
  604. if let tokenResult {
  605. continuation.resume(returning: tokenResult)
  606. } else if let error {
  607. continuation.resume(throwing: error)
  608. }
  609. }
  610. }
  611. }
  612. /// Associates a user account from a third-party identity provider with this user and
  613. /// returns additional identity provider data.
  614. ///
  615. /// Invoked asynchronously on the main thread in the future.
  616. ///
  617. /// Possible error codes:
  618. /// * `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a
  619. /// type already linked to this account.
  620. /// * `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a
  621. /// credential that has already been linked with a different Firebase account.
  622. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity
  623. /// provider represented by the credential are not enabled. Enable them in the Auth section
  624. /// of the Firebase console.
  625. ///
  626. /// This method may also return error codes associated with `updateEmail(to:)` and
  627. /// `updatePassword(to:)` on `User`.
  628. /// - Parameter credential: The credential for the identity provider.
  629. /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or
  630. /// fails.
  631. @objc(linkWithCredential:completion:)
  632. open func link(with credential: AuthCredential,
  633. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  634. kAuthGlobalWorkQueue.async {
  635. if self.providerDataRaw[credential.provider] != nil {
  636. User.callInMainThreadWithAuthDataResultAndError(
  637. callback: completion,
  638. result: nil,
  639. error: AuthErrorUtils.providerAlreadyLinkedError()
  640. )
  641. return
  642. }
  643. if let emailCredential = credential as? EmailAuthCredential {
  644. self.link(withEmailCredential: emailCredential, completion: completion)
  645. return
  646. }
  647. #if !os(watchOS)
  648. if let gameCenterCredential = credential as? GameCenterAuthCredential {
  649. self.link(withGameCenterCredential: gameCenterCredential, completion: completion)
  650. return
  651. }
  652. #endif
  653. #if os(iOS)
  654. if let phoneCredential = credential as? PhoneAuthCredential {
  655. self.link(withPhoneCredential: phoneCredential, completion: completion)
  656. return
  657. }
  658. #endif
  659. Task {
  660. do {
  661. let authDataResult = try await self.userProfileUpdate.link(user: self, with: credential)
  662. await MainActor.run {
  663. completion?(authDataResult, nil)
  664. }
  665. } catch {
  666. await MainActor.run {
  667. completion?(nil, error)
  668. }
  669. }
  670. }
  671. }
  672. }
  673. /// Associates a user account from a third-party identity provider with this user and
  674. /// returns additional identity provider data.
  675. ///
  676. /// Invoked asynchronously on the main thread in the future.
  677. ///
  678. /// Possible error codes:
  679. /// * `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a
  680. /// type already linked to this account.
  681. /// * `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a
  682. /// credential that has already been linked with a different Firebase account.
  683. /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity
  684. /// provider represented by the credential are not enabled. Enable them in the Auth section
  685. /// of the Firebase console.
  686. ///
  687. /// This method may also return error codes associated with `updateEmail(to:)` and
  688. /// `updatePassword(to:)` on `User`.
  689. /// - Parameter credential: The credential for the identity provider.
  690. /// - Returns: An `AuthDataResult`.
  691. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  692. @discardableResult
  693. open func link(with credential: AuthCredential) async throws -> AuthDataResult {
  694. return try await withCheckedThrowingContinuation { continuation in
  695. self.link(with: credential) { result, error in
  696. if let result {
  697. continuation.resume(returning: result)
  698. } else if let error {
  699. continuation.resume(throwing: error)
  700. }
  701. }
  702. }
  703. }
  704. #if os(iOS)
  705. /// Link the user with the provided auth provider instance.
  706. ///
  707. /// This method is available on iOSonly.
  708. /// - Parameter provider: An instance of an auth provider used to initiate the link flow.
  709. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  710. /// protocol used for presenting the web context. If nil, a default `AuthUIDelegate` will be
  711. /// used.
  712. /// - Parameter completion: Optionally; a block which is invoked when the link flow finishes, or
  713. /// is canceled. Invoked asynchronously on the main thread in the future.
  714. @objc(linkWithProvider:UIDelegate:completion:)
  715. open func link(with provider: FederatedAuthProvider,
  716. uiDelegate: AuthUIDelegate?,
  717. completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
  718. kAuthGlobalWorkQueue.async {
  719. Task {
  720. do {
  721. let credential = try await provider.credential(with: uiDelegate)
  722. self.link(with: credential, completion: completion)
  723. } catch {
  724. if let completion {
  725. completion(nil, error)
  726. }
  727. }
  728. }
  729. }
  730. }
  731. /// Link the user with the provided auth provider instance.
  732. ///
  733. /// This method is available on iOSonly.
  734. /// - Parameter provider: An instance of an auth provider used to initiate the link flow.
  735. /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate`
  736. /// protocol used for presenting the web context. If nil, a default `AuthUIDelegate`
  737. /// will be used.
  738. /// - Returns: An AuthDataResult.
  739. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  740. @discardableResult
  741. open func link(with provider: FederatedAuthProvider,
  742. uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
  743. return try await withCheckedThrowingContinuation { continuation in
  744. self.link(with: provider, uiDelegate: uiDelegate) { result, error in
  745. if let result {
  746. continuation.resume(returning: result)
  747. } else if let error {
  748. continuation.resume(throwing: error)
  749. }
  750. }
  751. }
  752. }
  753. #endif
  754. /// Disassociates a user account from a third-party identity provider with this user.
  755. ///
  756. /// Invoked asynchronously on the main thread in the future.
  757. ///
  758. /// Possible error codes:
  759. /// * `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider
  760. /// that is not linked to the account.
  761. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  762. /// operation that requires a recent login from the user. This error indicates the user
  763. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  764. /// `reauthenticate(with:)`.
  765. /// - Parameter provider: The provider ID of the provider to unlink.
  766. /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or
  767. /// fails.
  768. @objc open func unlink(fromProvider provider: String,
  769. completion: ((User?, Error?) -> Void)? = nil) {
  770. Task {
  771. do {
  772. let user = try await unlink(fromProvider: provider)
  773. await MainActor.run {
  774. completion?(user, nil)
  775. }
  776. } catch {
  777. await MainActor.run {
  778. completion?(nil, error)
  779. }
  780. }
  781. }
  782. }
  783. /// Disassociates a user account from a third-party identity provider with this user.
  784. ///
  785. /// Invoked asynchronously on the main thread in the future.
  786. ///
  787. /// Possible error codes:
  788. /// * `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider
  789. /// that is not linked to the account.
  790. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  791. /// operation that requires a recent login from the user. This error indicates the user
  792. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  793. /// `reauthenticate(with:)`.
  794. /// - Parameter provider: The provider ID of the provider to unlink.
  795. /// - Returns: The user.
  796. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  797. open func unlink(fromProvider provider: String) async throws -> User {
  798. return try await userProfileUpdate.unlink(user: self, fromProvider: provider)
  799. }
  800. /// Initiates email verification for the user.
  801. ///
  802. /// Possible error codes:
  803. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  804. /// sent in the request.
  805. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  806. /// the console for this action.
  807. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  808. /// sending update email.
  809. /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
  810. /// - Parameter completion: Optionally; the block invoked when the request to send an email
  811. /// verification is complete, or fails. Invoked asynchronously on the main thread in the future.
  812. @objc(sendEmailVerificationWithCompletion:)
  813. open func __sendEmailVerification(withCompletion completion: ((Error?) -> Void)?) {
  814. sendEmailVerification(completion: completion)
  815. }
  816. /// Initiates email verification for the user.
  817. ///
  818. /// Possible error codes:
  819. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  820. /// sent in the request.
  821. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  822. /// the console for this action.
  823. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  824. /// sending update email.
  825. /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
  826. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  827. /// handling action codes.
  828. /// - Parameter completion: Optionally; the block invoked when the request to send an email
  829. /// verification is complete, or fails. Invoked asynchronously on the main thread in the future.
  830. @objc(sendEmailVerificationWithActionCodeSettings:completion:)
  831. open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil,
  832. completion: ((Error?) -> Void)? = nil) {
  833. kAuthGlobalWorkQueue.async {
  834. self.internalGetToken(backend: self.backend) { accessToken, error in
  835. if let error {
  836. User.callInMainThreadWithError(callback: completion, error: error)
  837. return
  838. }
  839. guard let accessToken else {
  840. fatalError("Internal Error: Both error and accessToken are nil.")
  841. }
  842. guard let requestConfiguration = self.auth?.requestConfiguration else {
  843. fatalError("Internal Error: Unexpected nil requestConfiguration.")
  844. }
  845. let request = GetOOBConfirmationCodeRequest.verifyEmailRequest(
  846. accessToken: accessToken,
  847. actionCodeSettings: actionCodeSettings,
  848. requestConfiguration: requestConfiguration
  849. )
  850. Task {
  851. do {
  852. let _ = try await self.backend.call(with: request)
  853. User.callInMainThreadWithError(callback: completion, error: nil)
  854. } catch {
  855. self.signOutIfTokenIsInvalid(withError: error)
  856. User.callInMainThreadWithError(callback: completion, error: error)
  857. }
  858. }
  859. }
  860. }
  861. }
  862. /// Initiates email verification for the user.
  863. ///
  864. /// Possible error codes:
  865. /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
  866. /// sent in the request.
  867. /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
  868. /// the console for this action.
  869. /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
  870. /// sending update email.
  871. /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
  872. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  873. /// handling action codes. The default value is `nil`.
  874. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  875. open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil) async throws {
  876. return try await withCheckedThrowingContinuation { continuation in
  877. self.sendEmailVerification(with: actionCodeSettings) { error in
  878. if let error {
  879. continuation.resume(throwing: error)
  880. } else {
  881. continuation.resume()
  882. }
  883. }
  884. }
  885. }
  886. /// Deletes the user account (also signs out the user, if this was the current user).
  887. ///
  888. /// Possible error codes:
  889. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  890. /// operation that requires a recent login from the user. This error indicates the user
  891. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  892. /// `reauthenticate(with:)`.
  893. /// - Parameter completion: Optionally; the block invoked when the request to delete the account
  894. /// is complete, or fails. Invoked asynchronously on the main thread in the future.
  895. @objc open func delete(completion: ((Error?) -> Void)? = nil) {
  896. kAuthGlobalWorkQueue.async {
  897. self.internalGetToken(backend: self.backend) { accessToken, error in
  898. if let error {
  899. User.callInMainThreadWithError(callback: completion, error: error)
  900. return
  901. }
  902. guard let accessToken else {
  903. fatalError("Auth Internal Error: Both error and accessToken are nil.")
  904. }
  905. guard let requestConfiguration = self.auth?.requestConfiguration else {
  906. fatalError("Auth Internal Error: Unexpected nil requestConfiguration.")
  907. }
  908. let request = DeleteAccountRequest(localID: self.uid, accessToken: accessToken,
  909. requestConfiguration: requestConfiguration)
  910. Task {
  911. do {
  912. let _ = try await self.backend.call(with: request)
  913. try self.auth?.signOutByForce(withUserID: self.uid)
  914. User.callInMainThreadWithError(callback: completion, error: nil)
  915. } catch {
  916. User.callInMainThreadWithError(callback: completion, error: error)
  917. }
  918. }
  919. }
  920. }
  921. }
  922. /// Deletes the user account (also signs out the user, if this was the current user).
  923. ///
  924. /// Possible error codes:
  925. /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
  926. /// operation that requires a recent login from the user. This error indicates the user
  927. /// has not signed in recently enough. To resolve, reauthenticate the user by calling
  928. /// `reauthenticate(with:)`.
  929. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  930. open func delete() async throws {
  931. return try await withCheckedThrowingContinuation { continuation in
  932. self.delete { error in
  933. if let error {
  934. continuation.resume(throwing: error)
  935. } else {
  936. continuation.resume()
  937. }
  938. }
  939. }
  940. }
  941. /// Send an email to verify the ownership of the account then update to the new email.
  942. /// - Parameter email: The email to be updated to.
  943. /// - Parameter completion: Optionally; the block invoked when the request to send the
  944. /// verification email is complete, or fails.
  945. @objc(sendEmailVerificationBeforeUpdatingEmail:completion:)
  946. open func __sendEmailVerificationBeforeUpdating(email: String, completion: ((Error?) -> Void)?) {
  947. sendEmailVerification(beforeUpdatingEmail: email, completion: completion)
  948. }
  949. /// Send an email to verify the ownership of the account then update to the new email.
  950. /// - Parameter email: The email to be updated to.
  951. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  952. /// handling action codes.
  953. /// - Parameter completion: Optionally; the block invoked when the request to send the
  954. /// verification email is complete, or fails.
  955. @objc open func sendEmailVerification(beforeUpdatingEmail email: String,
  956. actionCodeSettings: ActionCodeSettings? = nil,
  957. completion: ((Error?) -> Void)? = nil) {
  958. kAuthGlobalWorkQueue.async {
  959. self.internalGetToken(backend: self.backend) { accessToken, error in
  960. if let error {
  961. User.callInMainThreadWithError(callback: completion, error: error)
  962. return
  963. }
  964. guard let accessToken else {
  965. fatalError("Internal Error: Both error and accessToken are nil.")
  966. }
  967. guard let requestConfiguration = self.auth?.requestConfiguration else {
  968. fatalError("Internal Error: Unexpected nil requestConfiguration.")
  969. }
  970. let request = GetOOBConfirmationCodeRequest.verifyBeforeUpdateEmail(
  971. accessToken: accessToken,
  972. newEmail: email,
  973. actionCodeSettings: actionCodeSettings,
  974. requestConfiguration: requestConfiguration
  975. )
  976. Task {
  977. do {
  978. let _ = try await self.backend.call(with: request)
  979. User.callInMainThreadWithError(callback: completion, error: nil)
  980. } catch {
  981. User.callInMainThreadWithError(callback: completion, error: error)
  982. }
  983. }
  984. }
  985. }
  986. }
  987. /// Send an email to verify the ownership of the account then update to the new email.
  988. /// - Parameter newEmail: The email to be updated to.
  989. /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to
  990. /// handling action codes.
  991. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  992. open func sendEmailVerification(beforeUpdatingEmail newEmail: String,
  993. actionCodeSettings: ActionCodeSettings? = nil) async throws {
  994. return try await withCheckedThrowingContinuation { continuation in
  995. self.sendEmailVerification(beforeUpdatingEmail: newEmail,
  996. actionCodeSettings: actionCodeSettings) { error in
  997. if let error {
  998. continuation.resume(throwing: error)
  999. } else {
  1000. continuation.resume()
  1001. }
  1002. }
  1003. }
  1004. }
  1005. // MARK: Passkey Implementation
  1006. #if os(iOS) || os(tvOS) || os(macOS) || targetEnvironment(macCatalyst)
  1007. /// A cached passkey name being passed from startPasskeyEnrollment(withName:) call and consumed
  1008. /// at finalizePasskeyEnrollment(withPlatformCredential:) call
  1009. private var passkeyName: String?
  1010. private let defaultPasskeyName: String = "Unnamed account (Apple)"
  1011. /// Start the passkey enrollment creating a plaform public key creation request with the
  1012. /// challenge from GCIP backend.
  1013. /// - Parameter name: The name for the passkey to be created.
  1014. @available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
  1015. public func startPasskeyEnrollment(withName name: String?) async throws
  1016. -> ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest {
  1017. guard auth != nil else {
  1018. /// If auth is nil, this User object is in an invalid state for this operation.
  1019. fatalError(
  1020. "Firebase Auth Internal Error: Set user's auth property with non-nil instance. Cannot start passkey enrollment."
  1021. )
  1022. }
  1023. let enrollmentIdToken = rawAccessToken()
  1024. let request = StartPasskeyEnrollmentRequest(
  1025. idToken: enrollmentIdToken,
  1026. requestConfiguration: requestConfiguration
  1027. )
  1028. let response = try await backend.call(with: request)
  1029. passkeyName = (name?.isEmpty ?? true) ? defaultPasskeyName : name
  1030. guard let challengeInData = Data(base64Encoded: response.challenge) else {
  1031. throw NSError(
  1032. domain: AuthErrorDomain,
  1033. code: AuthInternalErrorCode.RPCResponseDecodingError.rawValue,
  1034. userInfo: [NSLocalizedDescriptionKey: "Failed to decode base64 challenge from response."]
  1035. )
  1036. }
  1037. guard let userIdInData = Data(base64Encoded: response.userID) else {
  1038. throw NSError(
  1039. domain: AuthErrorDomain,
  1040. code: AuthInternalErrorCode.RPCResponseDecodingError.rawValue,
  1041. userInfo: [NSLocalizedDescriptionKey: "Failed to decode base64 userId from response."]
  1042. )
  1043. }
  1044. let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
  1045. relyingPartyIdentifier: response.rpID
  1046. )
  1047. return provider.createCredentialRegistrationRequest(
  1048. challenge: challengeInData,
  1049. name: passkeyName ?? defaultPasskeyName,
  1050. userID: userIdInData
  1051. )
  1052. }
  1053. /// Finalize the passkey enrollment with the platfrom public key credential.
  1054. /// - Parameter platformCredential: The name for the passkey to be created.
  1055. @available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
  1056. public func finalizePasskeyEnrollment(withPlatformCredential platformCredential: ASAuthorizationPlatformPublicKeyCredentialRegistration) async throws
  1057. -> AuthDataResult {
  1058. guard
  1059. !platformCredential.credentialID.isEmpty,
  1060. !platformCredential.rawClientDataJSON.isEmpty,
  1061. let attestation = platformCredential.rawAttestationObject,
  1062. !attestation.isEmpty
  1063. else {
  1064. throw NSError(
  1065. domain: AuthErrorDomain,
  1066. code: AuthErrorCode.internalError.rawValue,
  1067. userInfo: [NSLocalizedDescriptionKey:
  1068. "Invalid platform credential: missing credentialID, clientDataJSON, or attestationObject."]
  1069. )
  1070. }
  1071. let credentialId = platformCredential.credentialID.base64EncodedString()
  1072. let clientDataJson = platformCredential.rawClientDataJSON.base64EncodedString()
  1073. let attestationObject = platformCredential.rawAttestationObject!.base64EncodedString()
  1074. let request = FinalizePasskeyEnrollmentRequest(
  1075. idToken: rawAccessToken(),
  1076. name: passkeyName ?? defaultPasskeyName,
  1077. credentialID: credentialId,
  1078. clientDataJSON: clientDataJson,
  1079. attestationObject: attestationObject,
  1080. requestConfiguration: auth!.requestConfiguration
  1081. )
  1082. let response = try await backend.call(with: request)
  1083. let user = try await auth!.completeSignIn(
  1084. withAccessToken: response.idToken,
  1085. accessTokenExpirationDate: nil,
  1086. refreshToken: response.refreshToken,
  1087. anonymous: false
  1088. )
  1089. defer { self.passkeyName = nil }
  1090. try await user.reload()
  1091. try await auth!.updateCurrentUser(user)
  1092. return AuthDataResult(withUser: user, additionalUserInfo: nil)
  1093. }
  1094. @available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
  1095. public func unenrollPasskey(withCredentialID credentialID: String) async throws {
  1096. guard !credentialID.isEmpty else {
  1097. throw AuthErrorCode.missingPasskeyEnrollment
  1098. }
  1099. let request = SetAccountInfoRequest(
  1100. requestConfiguration: auth!.requestConfiguration
  1101. )
  1102. request.deletePasskeys = [credentialID]
  1103. request.accessToken = rawAccessToken()
  1104. let response = try await backend.call(with: request)
  1105. let user = try await auth!.completeSignIn(
  1106. withAccessToken: response.idToken,
  1107. accessTokenExpirationDate: response.approximateExpirationDate,
  1108. refreshToken: response.refreshToken,
  1109. anonymous: false
  1110. )
  1111. try await user.reload()
  1112. try await auth!.updateCurrentUser(user)
  1113. }
  1114. #endif
  1115. // MARK: Internal implementations below
  1116. func rawAccessToken() -> String {
  1117. return tokenService.accessToken
  1118. }
  1119. func accessTokenExpirationDate() -> Date? {
  1120. return tokenService.accessTokenExpirationDate
  1121. }
  1122. init(withTokenService tokenService: SecureTokenService, backend: AuthBackend) {
  1123. self.backend = backend
  1124. providerDataRaw = [:]
  1125. userProfileUpdate = UserProfileUpdate()
  1126. self.tokenService = tokenService
  1127. isAnonymous = false
  1128. isEmailVerified = false
  1129. metadata = UserMetadata(withCreationDate: nil, lastSignInDate: nil)
  1130. tenantID = nil
  1131. #if os(iOS)
  1132. multiFactor = MultiFactor(withMFAEnrollments: [])
  1133. enrolledPasskeys = []
  1134. #endif
  1135. uid = ""
  1136. hasEmailPasswordCredential = false
  1137. requestConfiguration = AuthRequestConfiguration(apiKey: "", appID: "")
  1138. }
  1139. class func retrieveUser(withAuth auth: Auth,
  1140. accessToken: String?,
  1141. accessTokenExpirationDate: Date?,
  1142. refreshToken: String?,
  1143. anonymous: Bool) async throws -> User {
  1144. guard let accessToken = accessToken,
  1145. let refreshToken = refreshToken else {
  1146. throw AuthErrorUtils
  1147. .invalidUserTokenError(message: "Invalid user token: accessToken or refreshToken is nil")
  1148. }
  1149. let tokenService = SecureTokenService(withRequestConfiguration: auth.requestConfiguration,
  1150. accessToken: accessToken,
  1151. accessTokenExpirationDate: accessTokenExpirationDate,
  1152. refreshToken: refreshToken)
  1153. let user = User(withTokenService: tokenService, backend: auth.backend)
  1154. user.auth = auth
  1155. user.tenantID = auth.tenantID
  1156. user.requestConfiguration = auth.requestConfiguration
  1157. let accessToken2 = try await user.internalGetTokenAsync(backend: user.backend)
  1158. let getAccountInfoRequest = GetAccountInfoRequest(
  1159. accessToken: accessToken2,
  1160. requestConfiguration: user.requestConfiguration
  1161. )
  1162. let response = try await auth.backend.call(with: getAccountInfoRequest)
  1163. user.isAnonymous = anonymous
  1164. user.update(withGetAccountInfoResponse: response)
  1165. return user
  1166. }
  1167. @objc open var providerID: String {
  1168. return "Firebase"
  1169. }
  1170. /// The provider's user ID for the user.
  1171. @objc open var uid: String
  1172. /// The name of the user.
  1173. @objc open var displayName: String?
  1174. /// The URL of the user's profile photo.
  1175. @objc open var photoURL: URL?
  1176. /// The user's email address.
  1177. @objc open var email: String?
  1178. /// A phone number associated with the user.
  1179. ///
  1180. /// This property is only available for users authenticated via phone number auth.
  1181. @objc open var phoneNumber: String?
  1182. /// Whether or not the user can be authenticated by using Firebase email and password.
  1183. var hasEmailPasswordCredential: Bool
  1184. /// Used to serialize the update profile calls.
  1185. private let userProfileUpdate: UserProfileUpdate
  1186. /// A strong reference to a requestConfiguration instance associated with this user instance.
  1187. var requestConfiguration: AuthRequestConfiguration
  1188. /// A secure token service associated with this user. For performing token exchanges and
  1189. /// refreshing access tokens.
  1190. var tokenService: SecureTokenService
  1191. private weak var _auth: Auth?
  1192. /// A weak reference to an `Auth` instance associated with this instance.
  1193. weak var auth: Auth? {
  1194. set {
  1195. guard let newValue else {
  1196. fatalError("Firebase Auth Internal Error: Set user's auth property with non-nil instance.")
  1197. }
  1198. _auth = newValue
  1199. requestConfiguration = newValue.requestConfiguration
  1200. tokenService.requestConfiguration = requestConfiguration
  1201. backend = newValue.backend
  1202. }
  1203. get { return _auth }
  1204. }
  1205. // MARK: Private functions
  1206. private func updateEmail(email: String?,
  1207. password: String?,
  1208. callback: @escaping (Error?) -> Void) {
  1209. let hadEmailPasswordCredential = hasEmailPasswordCredential
  1210. executeUserUpdateWithChanges(changeBlock: { user, request in
  1211. if let email {
  1212. request.email = email
  1213. }
  1214. if let password {
  1215. request.password = password
  1216. }
  1217. }) { error in
  1218. if let error {
  1219. callback(error)
  1220. return
  1221. }
  1222. if let email {
  1223. self.email = email
  1224. }
  1225. if self.email != nil {
  1226. if !hadEmailPasswordCredential {
  1227. // The list of providers need to be updated for the newly added email-password provider.
  1228. Task {
  1229. do {
  1230. let accessToken = try await self.internalGetTokenAsync(backend: self.backend)
  1231. if let requestConfiguration = self.auth?.requestConfiguration {
  1232. let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken,
  1233. requestConfiguration: requestConfiguration)
  1234. do {
  1235. let accountInfoResponse = try await self.backend.call(with: getAccountInfoRequest)
  1236. if let users = accountInfoResponse.users {
  1237. for userAccountInfo in users {
  1238. // Set the account to non-anonymous if there are any providers, even if
  1239. // they're not email/password ones.
  1240. if let providerUsers = userAccountInfo.providerUserInfo {
  1241. if providerUsers.count > 0 {
  1242. self.isAnonymous = false
  1243. for providerUserInfo in providerUsers {
  1244. if providerUserInfo.providerID == EmailAuthProvider.id {
  1245. self.hasEmailPasswordCredential = true
  1246. break
  1247. }
  1248. }
  1249. }
  1250. }
  1251. }
  1252. }
  1253. self.update(withGetAccountInfoResponse: accountInfoResponse)
  1254. if let error = self.updateKeychain() {
  1255. callback(error)
  1256. return
  1257. }
  1258. callback(nil)
  1259. } catch {
  1260. self.signOutIfTokenIsInvalid(withError: error)
  1261. callback(error)
  1262. }
  1263. }
  1264. } catch {
  1265. callback(error)
  1266. }
  1267. }
  1268. return
  1269. }
  1270. }
  1271. if let error = self.updateKeychain() {
  1272. callback(error)
  1273. return
  1274. }
  1275. callback(nil)
  1276. }
  1277. }
  1278. /// Performs a setAccountInfo request by mutating the results of a getAccountInfo response,
  1279. /// atomically in regards to other calls to this method.
  1280. /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest`
  1281. /// - Parameter callback: A block to invoke when the change is complete. Invoked asynchronously on
  1282. /// the auth global work queue in the future.
  1283. func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponse.User,
  1284. SetAccountInfoRequest) -> Void,
  1285. callback: @escaping (Error?) -> Void) {
  1286. Task {
  1287. do {
  1288. try await userProfileUpdate.executeUserUpdateWithChanges(user: self,
  1289. changeBlock: changeBlock)
  1290. await MainActor.run {
  1291. callback(nil)
  1292. }
  1293. } catch {
  1294. await MainActor.run {
  1295. callback(error)
  1296. }
  1297. }
  1298. }
  1299. }
  1300. /// Gets the users' account data from the server, updating our local values.
  1301. /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an
  1302. /// error has been detected. Invoked asynchronously on the auth global work queue in the future.
  1303. func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponse.User?,
  1304. Error?) -> Void) {
  1305. Task {
  1306. do {
  1307. let responseUser = try await userProfileUpdate.getAccountInfoRefreshingCache(self)
  1308. await MainActor.run {
  1309. callback(responseUser, nil)
  1310. }
  1311. } catch {
  1312. await MainActor.run {
  1313. callback(nil, error)
  1314. }
  1315. }
  1316. }
  1317. }
  1318. func update(withGetAccountInfoResponse response: GetAccountInfoResponse) {
  1319. guard let user = response.users?.first else {
  1320. // Silent fallthrough in ObjC code.
  1321. AuthLog.logWarning(code: "I-AUT000016", message: "Missing user in GetAccountInfoResponse")
  1322. return
  1323. }
  1324. uid = user.localID ?? ""
  1325. email = user.email
  1326. isEmailVerified = user.emailVerified
  1327. displayName = user.displayName
  1328. photoURL = user.photoURL
  1329. phoneNumber = user.phoneNumber
  1330. hasEmailPasswordCredential = user.passwordHash != nil && user.passwordHash!.count > 0
  1331. metadata = UserMetadata(withCreationDate: user.creationDate,
  1332. lastSignInDate: user.lastLoginDate)
  1333. var providerData: [String: UserInfoImpl] = [:]
  1334. if let providerUserInfos = user.providerUserInfo {
  1335. for providerUserInfo in providerUserInfos {
  1336. let userInfo = UserInfoImpl.userInfo(
  1337. withGetAccountInfoResponseProviderUserInfo: providerUserInfo
  1338. )
  1339. if let providerID = providerUserInfo.providerID {
  1340. providerData[providerID] = userInfo
  1341. }
  1342. }
  1343. }
  1344. providerDataRaw = providerData
  1345. #if os(iOS)
  1346. if let enrollments = user.mfaEnrollments {
  1347. multiFactor = MultiFactor(withMFAEnrollments: enrollments)
  1348. }
  1349. multiFactor.user = self
  1350. enrolledPasskeys = user.enrolledPasskeys ?? []
  1351. #endif
  1352. }
  1353. #if os(iOS)
  1354. /// Updates the phone number for the user. On success, the cached user profile data is updated.
  1355. ///
  1356. /// Invoked asynchronously on the global work queue in the future.
  1357. /// - Parameter credential: The new phone number credential corresponding to the phone
  1358. /// number to be added to the Firebase account. If a phone number is already linked to the
  1359. /// account, this new phone number will replace it.
  1360. /// - Parameter isLinkOperation: Boolean value indicating whether or not this is a link
  1361. /// operation.
  1362. /// - Parameter completion: Optionally; the block invoked when the user profile change has
  1363. /// finished.
  1364. private func internalUpdateOrLinkPhoneNumber(credential: PhoneAuthCredential,
  1365. isLinkOperation: Bool,
  1366. completion: @escaping (Error?) -> Void) {
  1367. internalGetToken(backend: backend) { accessToken, error in
  1368. if let error {
  1369. completion(error)
  1370. return
  1371. }
  1372. guard let accessToken = accessToken else {
  1373. fatalError("Auth Internal Error: Both accessToken and error are nil")
  1374. }
  1375. guard let configuration = self.auth?.requestConfiguration else {
  1376. fatalError("Auth Internal Error: nil value for VerifyPhoneNumberRequest initializer")
  1377. }
  1378. switch credential.credentialKind {
  1379. case .phoneNumber: fatalError("Internal Error: Missing verificationCode")
  1380. case let .verification(verificationID, code):
  1381. let operation = isLinkOperation ? AuthOperationType.link : AuthOperationType.update
  1382. let request = VerifyPhoneNumberRequest(verificationID: verificationID,
  1383. verificationCode: code,
  1384. operation: operation,
  1385. requestConfiguration: configuration)
  1386. request.accessToken = accessToken
  1387. Task {
  1388. do {
  1389. let verifyResponse = try await self.backend.call(with: request)
  1390. guard let idToken = verifyResponse.idToken,
  1391. let refreshToken = verifyResponse.refreshToken else {
  1392. fatalError("Internal Auth Error: missing token in internalUpdateOrLinkPhoneNumber")
  1393. }
  1394. // Update the new token and refresh user info again.
  1395. self.tokenService = SecureTokenService(
  1396. withRequestConfiguration: configuration,
  1397. accessToken: idToken,
  1398. accessTokenExpirationDate: verifyResponse.approximateExpirationDate,
  1399. refreshToken: refreshToken
  1400. )
  1401. // Get account info to update cached user info.
  1402. self.getAccountInfoRefreshingCache { user, error in
  1403. if let error {
  1404. self.signOutIfTokenIsInvalid(withError: error)
  1405. completion(error)
  1406. return
  1407. }
  1408. self.isAnonymous = false
  1409. if let error = self.updateKeychain() {
  1410. completion(error)
  1411. return
  1412. }
  1413. completion(nil)
  1414. }
  1415. } catch {
  1416. self.signOutIfTokenIsInvalid(withError: error)
  1417. completion(error)
  1418. }
  1419. }
  1420. }
  1421. }
  1422. }
  1423. #endif
  1424. private func link(withEmail email: String,
  1425. password: String,
  1426. authResult: AuthDataResult,
  1427. _ completion: ((AuthDataResult?, Error?) -> Void)?) {
  1428. internalGetToken(backend: backend) { accessToken, error in
  1429. guard let requestConfiguration = self.auth?.requestConfiguration else {
  1430. fatalError("Internal auth error: missing auth on User")
  1431. }
  1432. let request = SignUpNewUserRequest(email: email,
  1433. password: password,
  1434. displayName: nil,
  1435. idToken: accessToken,
  1436. requestConfiguration: requestConfiguration)
  1437. Task {
  1438. do {
  1439. #if os(iOS)
  1440. guard let auth = self.auth else {
  1441. fatalError("Internal Auth error: missing auth instance on user")
  1442. }
  1443. let response = try await auth.injectRecaptcha(request: request,
  1444. action: AuthRecaptchaAction
  1445. .signUpPassword)
  1446. #else
  1447. let response = try await self.backend.call(with: request)
  1448. #endif
  1449. guard let refreshToken = response.refreshToken,
  1450. let idToken = response.idToken else {
  1451. fatalError("Internal auth error: Invalid SignUpNewUserResponse")
  1452. }
  1453. // Update the new token and refresh user info again.
  1454. try await self.updateTokenAndRefreshUser(
  1455. idToken: idToken,
  1456. refreshToken: refreshToken,
  1457. expirationDate: response.approximateExpirationDate,
  1458. requestConfiguration: requestConfiguration
  1459. )
  1460. User.callInMainThreadWithAuthDataResultAndError(
  1461. callback: completion,
  1462. result: AuthDataResult(withUser: self, additionalUserInfo: nil),
  1463. error: nil
  1464. )
  1465. } catch {
  1466. self.signOutIfTokenIsInvalid(withError: error)
  1467. User.callInMainThreadWithAuthDataResultAndError(callback: completion,
  1468. result: nil, error: error)
  1469. }
  1470. }
  1471. }
  1472. }
  1473. private func link(withEmailCredential emailCredential: EmailAuthCredential,
  1474. completion: ((AuthDataResult?, Error?) -> Void)?) {
  1475. if hasEmailPasswordCredential {
  1476. User.callInMainThreadWithAuthDataResultAndError(
  1477. callback: completion,
  1478. result: nil,
  1479. error: AuthErrorUtils
  1480. .providerAlreadyLinkedError()
  1481. )
  1482. return
  1483. }
  1484. switch emailCredential.emailType {
  1485. case let .password(password):
  1486. let result = AuthDataResult(withUser: self, additionalUserInfo: nil)
  1487. link(withEmail: emailCredential.email, password: password, authResult: result, completion)
  1488. case let .link(link):
  1489. internalGetToken(backend: backend) { accessToken, error in
  1490. var queryItems = AuthWebUtils.parseURL(link)
  1491. if link.count == 0 {
  1492. if let urlComponents = URLComponents(string: link),
  1493. let query = urlComponents.query {
  1494. queryItems = AuthWebUtils.parseURL(query)
  1495. }
  1496. }
  1497. guard let actionCode = queryItems["oobCode"],
  1498. let requestConfiguration = self.auth?.requestConfiguration else {
  1499. fatalError("Internal Auth Error: Missing oobCode or requestConfiguration")
  1500. }
  1501. let request = EmailLinkSignInRequest(email: emailCredential.email,
  1502. oobCode: actionCode,
  1503. requestConfiguration: requestConfiguration)
  1504. request.idToken = accessToken
  1505. Task {
  1506. do {
  1507. let response = try await self.backend.call(with: request)
  1508. guard let idToken = response.idToken,
  1509. let refreshToken = response.refreshToken else {
  1510. fatalError("Internal Auth Error: missing token in EmailLinkSignInResponse")
  1511. }
  1512. try await self.updateTokenAndRefreshUser(
  1513. idToken: idToken,
  1514. refreshToken: refreshToken,
  1515. expirationDate: response.approximateExpirationDate,
  1516. requestConfiguration: requestConfiguration
  1517. )
  1518. User.callInMainThreadWithAuthDataResultAndError(
  1519. callback: completion,
  1520. result: AuthDataResult(withUser: self, additionalUserInfo: nil),
  1521. error: nil
  1522. )
  1523. } catch {
  1524. User.callInMainThreadWithAuthDataResultAndError(callback: completion,
  1525. result: nil,
  1526. error: error)
  1527. }
  1528. }
  1529. }
  1530. }
  1531. }
  1532. #if !os(watchOS)
  1533. private func link(withGameCenterCredential gameCenterCredential: GameCenterAuthCredential,
  1534. completion: ((AuthDataResult?, Error?) -> Void)?) {
  1535. internalGetToken(backend: backend) { accessToken, error in
  1536. guard let requestConfiguration = self.auth?.requestConfiguration,
  1537. let publicKeyURL = gameCenterCredential.publicKeyURL,
  1538. let signature = gameCenterCredential.signature,
  1539. let salt = gameCenterCredential.salt else {
  1540. fatalError("Internal Auth Error: Nil value field for SignInWithGameCenterRequest")
  1541. }
  1542. let request = SignInWithGameCenterRequest(playerID: gameCenterCredential.playerID,
  1543. teamPlayerID: gameCenterCredential.teamPlayerID,
  1544. gamePlayerID: gameCenterCredential.gamePlayerID,
  1545. publicKeyURL: publicKeyURL,
  1546. signature: signature,
  1547. salt: salt,
  1548. timestamp: gameCenterCredential.timestamp,
  1549. displayName: gameCenterCredential.displayName,
  1550. requestConfiguration: requestConfiguration)
  1551. request.accessToken = accessToken
  1552. Task {
  1553. do {
  1554. let response = try await self.backend.call(with: request)
  1555. guard let idToken = response.idToken,
  1556. let refreshToken = response.refreshToken else {
  1557. fatalError("Internal Auth Error: missing token in link(withGameCredential")
  1558. }
  1559. try await self.updateTokenAndRefreshUser(
  1560. idToken: idToken,
  1561. refreshToken: refreshToken,
  1562. expirationDate: response.approximateExpirationDate,
  1563. requestConfiguration: requestConfiguration
  1564. )
  1565. User.callInMainThreadWithAuthDataResultAndError(
  1566. callback: completion,
  1567. result: AuthDataResult(withUser: self, additionalUserInfo: nil),
  1568. error: nil
  1569. )
  1570. } catch {
  1571. User.callInMainThreadWithAuthDataResultAndError(callback: completion,
  1572. result: nil,
  1573. error: error)
  1574. }
  1575. }
  1576. }
  1577. }
  1578. #endif
  1579. #if os(iOS)
  1580. private func link(withPhoneCredential phoneCredential: PhoneAuthCredential,
  1581. completion: ((AuthDataResult?, Error?) -> Void)?) {
  1582. internalUpdateOrLinkPhoneNumber(credential: phoneCredential,
  1583. isLinkOperation: true) { error in
  1584. if let error {
  1585. User.callInMainThreadWithAuthDataResultAndError(
  1586. callback: completion,
  1587. result: nil,
  1588. error: error
  1589. )
  1590. } else {
  1591. let result = AuthDataResult(withUser: self, additionalUserInfo: nil)
  1592. User.callInMainThreadWithAuthDataResultAndError(
  1593. callback: completion,
  1594. result: result,
  1595. error: nil
  1596. )
  1597. }
  1598. }
  1599. }
  1600. #endif
  1601. // Update the new token and refresh user info again.
  1602. private func updateTokenAndRefreshUser(idToken: String,
  1603. refreshToken: String,
  1604. expirationDate: Date?,
  1605. requestConfiguration: AuthRequestConfiguration) async throws {
  1606. return try await userProfileUpdate
  1607. .updateTokenAndRefreshUser(
  1608. user: self,
  1609. idToken: idToken,
  1610. refreshToken: refreshToken,
  1611. expirationDate: expirationDate
  1612. )
  1613. }
  1614. /// Signs out this user if the user or the token is invalid.
  1615. /// - Parameter error: The error from the server.
  1616. func signOutIfTokenIsInvalid(withError error: Error) {
  1617. let code = (error as NSError).code
  1618. if code == AuthErrorCode.userNotFound.rawValue ||
  1619. code == AuthErrorCode.userDisabled.rawValue ||
  1620. code == AuthErrorCode.invalidUserToken.rawValue ||
  1621. code == AuthErrorCode.userTokenExpired.rawValue {
  1622. AuthLog.logNotice(code: "I-AUT000016",
  1623. message: "Invalid user token detected, user is automatically signed out.")
  1624. try? auth?.signOutByForce(withUserID: uid)
  1625. }
  1626. }
  1627. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  1628. /// - Parameter callback: The block to invoke when the token is available. Invoked asynchronously
  1629. /// on the global work thread in the future.
  1630. func internalGetToken(forceRefresh: Bool = false,
  1631. backend: AuthBackend,
  1632. callback: @escaping (String?, Error?) -> Void,
  1633. callCallbackOnMain: Bool = false) {
  1634. Task {
  1635. do {
  1636. let token = try await internalGetTokenAsync(forceRefresh: forceRefresh, backend: backend)
  1637. if callCallbackOnMain {
  1638. Auth.wrapMainAsync(callback: callback, with: .success(token))
  1639. } else {
  1640. callback(token, nil)
  1641. }
  1642. } catch {
  1643. if callCallbackOnMain {
  1644. Auth.wrapMainAsync(callback: callback, with: .failure(error))
  1645. } else {
  1646. callback(nil, error)
  1647. }
  1648. }
  1649. }
  1650. }
  1651. /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  1652. /// - Parameter forceRefresh
  1653. func internalGetTokenAsync(forceRefresh: Bool = false,
  1654. backend: AuthBackend) async throws -> String {
  1655. var keychainError = false
  1656. do {
  1657. let (token, tokenUpdated) = try await tokenService.fetchAccessToken(
  1658. forcingRefresh: forceRefresh, backend: backend
  1659. )
  1660. if tokenUpdated {
  1661. if let error = updateKeychain() {
  1662. keychainError = true
  1663. throw error
  1664. }
  1665. }
  1666. return token!
  1667. } catch {
  1668. if !keychainError {
  1669. signOutIfTokenIsInvalid(withError: error)
  1670. }
  1671. throw error
  1672. }
  1673. }
  1674. /// Updates the keychain for user token or info changes.
  1675. /// - Returns: An `Error` on failure.
  1676. func updateKeychain() -> Error? {
  1677. return auth?.updateKeychain(withUser: self)
  1678. }
  1679. /// Calls a callback in main thread with error.
  1680. /// - Parameter callback: The callback to be called in main thread.
  1681. /// - Parameter error: The error to pass to callback.
  1682. class func callInMainThreadWithError(callback: ((Error?) -> Void)?, error: Error?) {
  1683. if let callback {
  1684. DispatchQueue.main.async {
  1685. callback(error)
  1686. }
  1687. }
  1688. }
  1689. /// Calls a callback in main thread with user and error.
  1690. /// - Parameter callback: The callback to be called in main thread.
  1691. /// - Parameter user: The user to pass to callback if there is no error.
  1692. /// - Parameter error: The error to pass to callback.
  1693. private class func callInMainThreadWithUserAndError(callback: ((User?, Error?) -> Void)?,
  1694. user: User,
  1695. error: Error?) {
  1696. if let callback {
  1697. DispatchQueue.main.async {
  1698. callback((error != nil) ? nil : user, error)
  1699. }
  1700. }
  1701. }
  1702. /// Calls a callback in main thread with user and error.
  1703. /// - Parameter callback: The callback to be called in main thread.
  1704. private class func callInMainThreadWithAuthDataResultAndError(callback: (
  1705. (AuthDataResult?, Error?) -> Void
  1706. )?,
  1707. result: AuthDataResult? = nil,
  1708. error: Error? = nil) {
  1709. if let callback {
  1710. DispatchQueue.main.async {
  1711. callback(result, error)
  1712. }
  1713. }
  1714. }
  1715. // MARK: NSSecureCoding
  1716. private let kUserIDCodingKey = "userID"
  1717. private let kHasEmailPasswordCredentialCodingKey = "hasEmailPassword"
  1718. private let kAnonymousCodingKey = "anonymous"
  1719. private let kEmailCodingKey = "email"
  1720. private let kPhoneNumberCodingKey = "phoneNumber"
  1721. private let kEmailVerifiedCodingKey = "emailVerified"
  1722. private let kDisplayNameCodingKey = "displayName"
  1723. private let kPhotoURLCodingKey = "photoURL"
  1724. private let kProviderDataKey = "providerData"
  1725. private let kAPIKeyCodingKey = "APIKey"
  1726. private let kFirebaseAppIDCodingKey = "firebaseAppID"
  1727. private let kTokenServiceCodingKey = "tokenService"
  1728. private let kMetadataCodingKey = "metadata"
  1729. private let kMultiFactorCodingKey = "multiFactor"
  1730. private let kTenantIDCodingKey = "tenantID"
  1731. private let kEnrolledPasskeysKey = "passkeys"
  1732. public static let supportsSecureCoding = true
  1733. public func encode(with coder: NSCoder) {
  1734. coder.encode(uid, forKey: kUserIDCodingKey)
  1735. coder.encode(isAnonymous, forKey: kAnonymousCodingKey)
  1736. coder.encode(hasEmailPasswordCredential, forKey: kHasEmailPasswordCredentialCodingKey)
  1737. coder.encode(providerDataRaw, forKey: kProviderDataKey)
  1738. coder.encode(email, forKey: kEmailCodingKey)
  1739. coder.encode(phoneNumber, forKey: kPhoneNumberCodingKey)
  1740. coder.encode(isEmailVerified, forKey: kEmailVerifiedCodingKey)
  1741. coder.encode(photoURL, forKey: kPhotoURLCodingKey)
  1742. coder.encode(displayName, forKey: kDisplayNameCodingKey)
  1743. coder.encode(metadata, forKey: kMetadataCodingKey)
  1744. coder.encode(tenantID, forKey: kTenantIDCodingKey)
  1745. if let auth {
  1746. coder.encode(auth.requestConfiguration.apiKey, forKey: kAPIKeyCodingKey)
  1747. coder.encode(auth.requestConfiguration.appID, forKey: kFirebaseAppIDCodingKey)
  1748. }
  1749. coder.encode(tokenService, forKey: kTokenServiceCodingKey)
  1750. #if os(iOS)
  1751. coder.encode(multiFactor, forKey: kMultiFactorCodingKey)
  1752. coder.encode(enrolledPasskeys, forKey: kEnrolledPasskeysKey)
  1753. #endif
  1754. }
  1755. public required init?(coder: NSCoder) {
  1756. guard let userID = coder.decodeObject(of: NSString.self, forKey: kUserIDCodingKey) as? String,
  1757. let tokenService = coder.decodeObject(of: SecureTokenService.self,
  1758. forKey: kTokenServiceCodingKey) else {
  1759. return nil
  1760. }
  1761. let anonymous = coder.decodeBool(forKey: kAnonymousCodingKey)
  1762. let hasEmailPasswordCredential = coder.decodeBool(forKey: kHasEmailPasswordCredentialCodingKey)
  1763. let displayName = coder.decodeObject(
  1764. of: NSString.self,
  1765. forKey: kDisplayNameCodingKey
  1766. ) as? String
  1767. let photoURL = coder.decodeObject(of: NSURL.self, forKey: kPhotoURLCodingKey) as? URL
  1768. let email = coder.decodeObject(of: NSString.self, forKey: kEmailCodingKey) as? String
  1769. let phoneNumber = coder.decodeObject(
  1770. of: NSString.self,
  1771. forKey: kPhoneNumberCodingKey
  1772. ) as? String
  1773. let emailVerified = coder.decodeBool(forKey: kEmailVerifiedCodingKey)
  1774. let classes = [NSDictionary.self, NSString.self, UserInfoImpl.self]
  1775. let providerData = coder.decodeObject(of: classes, forKey: kProviderDataKey)
  1776. as? [String: UserInfoImpl]
  1777. let metadata = coder.decodeObject(of: UserMetadata.self, forKey: kMetadataCodingKey)
  1778. let tenantID = coder.decodeObject(of: NSString.self, forKey: kTenantIDCodingKey) as? String
  1779. #if os(iOS)
  1780. let multiFactor = coder.decodeObject(of: MultiFactor.self, forKey: kMultiFactorCodingKey)
  1781. let passkeyAllowed: [AnyClass] = [NSArray.self, PasskeyInfo.self]
  1782. let passkeys = coder.decodeObject(of: passkeyAllowed,
  1783. forKey: kEnrolledPasskeysKey) as? [PasskeyInfo]
  1784. #endif
  1785. self.tokenService = tokenService
  1786. uid = userID
  1787. isAnonymous = anonymous
  1788. self.hasEmailPasswordCredential = hasEmailPasswordCredential
  1789. self.email = email
  1790. isEmailVerified = emailVerified
  1791. self.displayName = displayName
  1792. self.photoURL = photoURL
  1793. providerDataRaw = providerData ?? [:]
  1794. self.phoneNumber = phoneNumber
  1795. self.metadata = metadata ?? UserMetadata(withCreationDate: nil, lastSignInDate: nil)
  1796. self.tenantID = tenantID
  1797. // Note, in practice, the caller will set the `auth` property of this user
  1798. // instance which will as a side-effect overwrite the request configuration.
  1799. // The assignment here is a best-effort placeholder.
  1800. let apiKey = coder.decodeObject(of: NSString.self, forKey: kAPIKeyCodingKey) as? String
  1801. let appID = coder.decodeObject(
  1802. of: NSString.self,
  1803. forKey: kFirebaseAppIDCodingKey
  1804. ) as? String
  1805. requestConfiguration = AuthRequestConfiguration(apiKey: apiKey ?? "", appID: appID ?? "")
  1806. // This property will be overwritten later via the `user.auth` property update. For now, a
  1807. // placeholder is set as the property update should happen right after this initializer.
  1808. backend = AuthBackend(rpcIssuer: AuthBackendRPCIssuer())
  1809. userProfileUpdate = UserProfileUpdate()
  1810. #if os(iOS)
  1811. self.multiFactor = multiFactor ?? MultiFactor()
  1812. super.init()
  1813. multiFactor?.user = self
  1814. enrolledPasskeys = passkeys ?? []
  1815. #endif
  1816. }
  1817. }