User.swift 90 KB

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