User.swift 93 KB

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