| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085 |
- // Copyright 2023 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import Foundation
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- extension User: NSSecureCoding {}
- /** @class User
- @brief Represents a user. Firebase Auth does not attempt to validate users
- when loading them from the keychain. Invalidated users (such as those
- whose passwords have been changed on another client) are automatically
- logged out when an auth-dependent operation is attempted or when the
- ID token is automatically refreshed.
- @remarks This class is thread-safe.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- @objc(FIRUser) public class User: NSObject, UserInfo {
- /** @property anonymous
- @brief Indicates the user represents an anonymous user.
- */
- @objc public private(set) var isAnonymous: Bool
- @objc public func anonymous() -> Bool { return isAnonymous }
- /** @property emailVerified
- @brief Indicates the email address associated with this user has been verified.
- */
- @objc public private(set) var isEmailVerified: Bool
- @objc public func emailVerified() -> Bool { return isEmailVerified }
- /** @property providerData
- @brief Profile data for each identity provider, if any.
- @remarks This data is cached on sign-in and updated when linking or unlinking.
- */
- @objc public var providerData: [UserInfoImpl] {
- return Array(providerDataRaw.values)
- }
- private var providerDataRaw: [String: UserInfoImpl]
- /** @property metadata
- @brief Metadata associated with the Firebase user in question.
- */
- @objc public private(set) var metadata: UserMetadata
- /** @property tenantID
- @brief The tenant ID of the current user. nil if none is available.
- */
- @objc public private(set) var tenantID: String?
- #if os(iOS)
- /** @property multiFactor
- @brief Multi factor object associated with the user.
- This property is available on iOS only.
- */
- @objc public private(set) var multiFactor: MultiFactor
- #endif
- /** @fn updateEmail:completion:
- @brief Updates the email address for the user. On success, the cached user profile data is
- updated.
- @remarks May fail if there is already an account with this email address that was created using
- email and password authentication.
- @param email The email address for the user.
- @param completion Optionally; the block invoked when the user profile change has finished.
- Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
- sent in the request.
- + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
- the console for this action.
- + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
- sending update email.
- + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another
- account.
- + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
- + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security
- sensitive operation that requires a recent login from the user. This error indicates
- the user has not signed in recently enough. To resolve, reauthenticate the user by
- calling `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc(updateEmail:completion:)
- public func updateEmail(to email: String, completion: ((Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.updateEmail(email: email, password: nil) { error in
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- /** @fn updateEmail
- @brief Updates the email address for the user. On success, the cached user profile data is
- updated.
- @remarks May fail if there is already an account with this email address that was created using
- email and password authentication.
- @param email The email address for the user.
- @throws Error on failure.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
- sent in the request.
- + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
- the console for this action.
- + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
- sending update email.
- + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another
- account.
- + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
- + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security
- sensitive operation that requires a recent login from the user. This error indicates
- the user has not signed in recently enough. To resolve, reauthenticate the user by
- calling `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func updateEmail(to email: String) async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.updateEmail(to: email) { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- /** @fn updatePassword:completion:
- @brief Updates the password for the user. On success, the cached user profile data is updated.
- @param password The new password for the user.
- @param completion Optionally; the block invoked when the user profile change has finished.
- Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled
- sign in with the specified identity provider.
- + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security
- sensitive operation that requires a recent login from the user. This error indicates
- the user has not signed in recently enough. To resolve, reauthenticate the user by
- calling `reauthenticate(with:)`.
- + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
- considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
- dictionary object will contain more detailed explanation that can be shown to the user.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc(updatePassword:completion:)
- public func updatePassword(to password: String, completion: ((Error?) -> Void)? = nil) {
- guard password.count > 0 else {
- if let completion {
- completion(AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing Password"))
- }
- return
- }
- kAuthGlobalWorkQueue.async {
- self.updateEmail(email: nil, password: password) { error in
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- /** @fn updatePassword
- @brief Updates the password for the user. On success, the cached user profile data is updated.
- @param password The new password for the user.
- @throws Error on failure.
- @remarks Possible error codes:
- + `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled
- sign in with the specified identity provider.
- + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security
- sensitive operation that requires a recent login from the user. This error indicates
- the user has not signed in recently enough. To resolve, reauthenticate the user by
- calling `reauthenticate(with:)`.
- + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
- considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo`
- dictionary object will contain more detailed explanation that can be shown to the user.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func updatePassword(to password: String) async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.updatePassword(to: password) { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- #if os(iOS)
- /** @fn updatePhoneNumberCredential:completion:
- @brief Updates the phone number for the user. On success, the cached user profile data is
- updated.
- This method is available on iOS only.
- @param phoneNumberCredential The new phone number credential corresponding to the phone number
- to be added to the Firebase account, if a phone number is already linked to the account this
- new phone number will replace it.
- @param completion Optionally; the block invoked when the user profile change has finished.
- Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security
- sensitive operation that requires a recent login from the user. This error indicates
- the user has not signed in recently enough. To resolve, reauthenticate the user by
- calling `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc(updatePhoneNumberCredential:completion:)
- public func updatePhoneNumber(_ credential: PhoneAuthCredential,
- completion: ((Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.internalUpdateOrLinkPhoneNumber(credential: credential,
- isLinkOperation: false) { error in
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- /** @fn updatePhoneNumberCredential
- @brief Updates the phone number for the user. On success, the cached user profile data is
- updated.
- This method is available on iOS only.
- @param phoneNumberCredential The new phone number credential corresponding to the phone number
- to be added to the Firebase account, if a phone number is already linked to the account this
- new phone number will replace it.
- @throws an error.
- @remarks Possible error codes:
- + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security
- sensitive operation that requires a recent login from the user. This error indicates
- the user has not signed in recently enough. To resolve, reauthenticate the user by
- calling `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func updatePhoneNumber(_ credential: PhoneAuthCredential) async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.updatePhoneNumber(credential) { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- #endif
- /** @fn profileChangeRequest
- @brief Creates an object which may be used to change the user's profile data.
- @remarks Set the properties of the returned object, then call
- `UserProfileChangeRequest.commitChanges()` to perform the updates atomically.
- @return An object which may be used to change the user's profile data atomically.
- */
- @objc(profileChangeRequest)
- public func createProfileChangeRequest() -> UserProfileChangeRequest {
- var result: UserProfileChangeRequest?
- kAuthGlobalWorkQueue.sync {
- result = UserProfileChangeRequest(self)
- }
- // TODO: Is there a way to do without force unwrap?
- return result!
- }
- /** @property refreshToken
- @brief A refresh token; useful for obtaining new access tokens independently.
- @remarks This property should only be used for advanced scenarios, and is not typically needed.
- */
- @objc public var refreshToken: String? {
- var result: String?
- kAuthGlobalWorkQueue.sync {
- result = self.tokenService.refreshToken
- }
- return result
- }
- /** @fn reloadWithCompletion:
- @brief Reloads the user's profile data from the server.
- @param completion Optionally; the block invoked when the reload has finished. Invoked
- asynchronously on the main thread in the future.
- @remarks May fail with a `AuthErrorCodeRequiresRecentLogin` error code. In this case
- you should call `reauthenticate(with:)` before re-invoking
- `updateEmail(to:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @objc public func reload(withCompletion completion: ((Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.getAccountInfoRefreshingCache { user, error in
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- /** @fn reload
- @brief Reloads the user's profile data from the server.
- @param completion Optionally; the block invoked when the reload has finished. Invoked
- asynchronously on the main thread in the future.
- @remarks May fail with a `AuthErrorCodeRequiresRecentLogin` error code. In this case
- you should call `reauthenticate(with:)` before re-invoking
- `updateEmail(to:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func reload() async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.reload { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- /** @fn reauthenticateWithCredential:completion:
- @brief Renews the user's authentication tokens by validating a fresh set of credentials supplied
- by the user and returns additional identity provider data.
- @param credential A user-supplied credential, which will be validated by the server. This can be
- a successful third-party identity provider sign-in, or an email address and password.
- @param completion Optionally; the block invoked when the re-authentication operation has
- finished. Invoked asynchronously on the main thread in the future.
- @remarks If the user associated with the supplied credential is different from the current user,
- or if the validation of the supplied credentials fails; an error is returned and the current
- user remains signed in.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
- This could happen if it has expired or it is malformed.
- + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the
- identity provider represented by the credential are not enabled. Enable them in the
- Auth section of the Firebase console.
- + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential
- (e.g. the email in a Facebook access token) is already in use by an existing account,
- that cannot be authenticated with this method. Call `Auth.fetchSignInMethods(forEmail:)`
- for this user’s email and then prompt them to sign in with any of the sign-in providers
- returned. This error will only be thrown if the "One account per email address"
- setting is enabled in the Firebase console, under Auth settings. Please note that the
- error code raised in this specific situation may not be the same on Web and Android.
- + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
- + `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with
- an incorrect password, if credential is of the type `EmailPasswordAuthCredential`.
- + `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to
- reauthenticate with a user which is not the current user.
- + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @objc(reauthenticateWithCredential:completion:)
- public func reauthenticate(with credential: AuthCredential,
- completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.auth?.internalSignInAndRetrieveData(
- withCredential: credential,
- isReauthentication: true
- ) {
- authResult, error in
- if let error {
- // If "user not found" error returned by backend,
- // translate to user mismatch error which is more
- // accurate.
- var reportError: Error = error
- if (error as NSError).code == AuthErrorCode.userNotFound.rawValue {
- reportError = AuthErrorUtils.userMismatchError()
- }
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- result: authResult,
- error: reportError)
- return
- }
- guard let user = authResult?.user,
- user.uid == self.auth?.getUserID() else {
- User.callInMainThreadWithAuthDataResultAndError(
- callback: completion,
- result: authResult,
- error: AuthErrorUtils.userMismatchError()
- )
- return
- }
- // Successful reauthenticate
- self.setTokenService(tokenService: user.tokenService) { error in
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- result: authResult,
- error: error)
- }
- }
- }
- }
- /** @fn reauthenticateWithCredential
- @brief Renews the user's authentication tokens by validating a fresh set of credentials supplied
- by the user and returns additional identity provider data.
- @param credential A user-supplied credential, which will be validated by the server. This can be
- a successful third-party identity provider sign-in, or an email address and password.
- @returns An AuthDataResult.
- @remarks If the user associated with the supplied credential is different from the current user,
- or if the validation of the supplied credentials fails; an error is returned and the current
- user remains signed in.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid.
- This could happen if it has expired or it is malformed.
- + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the
- identity provider represented by the credential are not enabled. Enable them in the
- Auth section of the Firebase console.
- + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential
- (e.g. the email in a Facebook access token) is already in use by an existing account,
- that cannot be authenticated with this method. Call `Auth.fetchSignInMethods(forEmail:)`
- for this user’s email and then prompt them to sign in with any of the sign-in providers
- returned. This error will only be thrown if the "One account per email address"
- setting is enabled in the Firebase console, under Auth settings. Please note that the
- error code raised in this specific situation may not be the same on Web and Android.
- + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
- + `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with
- an incorrect password, if credential is of the type `EmailPasswordAuthCredential`.
- + `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to
- reauthenticate with a user which is not the current user.
- + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- @discardableResult
- public func reauthenticate(with credential: AuthCredential) async throws -> AuthDataResult {
- return try await withCheckedThrowingContinuation { continuation in
- self.reauthenticate(with: credential) { result, error in
- if let result {
- continuation.resume(returning: result)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- #if os(iOS)
- /** @fn reauthenticateWithProvider:UIDelegate:completion:
- @brief Renews the user's authentication using the provided auth provider instance.
- This method is available on iOS only.
- @param provider An instance of an auth provider used to initiate the reauthenticate flow.
- @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate`
- protocol, used for presenting the web context. If nil, a default `AuthUIDelegate`
- will be used.
- @param completion Optionally; a block which is invoked when the reauthenticate flow finishes, or
- is canceled. Invoked asynchronously on the main thread in the future.
- */
- @objc(reauthenticateWithProvider:UIDelegate:completion:)
- public func reauthenticate(with provider: FederatedAuthProvider,
- uiDelegate: AuthUIDelegate?,
- completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- provider.getCredentialWith(uiDelegate) { credential, error in
- if let error {
- if let completion {
- completion(nil, error)
- }
- return
- }
- if let credential {
- self.reauthenticate(with: credential, completion: completion)
- }
- }
- }
- }
- /** @fn reauthenticateWithProvider:UIDelegate
- @brief Renews the user's authentication using the provided auth provider instance.
- This method is available on iOS only.
- @param provider An instance of an auth provider used to initiate the reauthenticate flow.
- @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate`
- protocol, used for presenting the web context. If nil, a default `AuthUIDelegate`
- will be used.
- @returns An AuthDataResult.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- @discardableResult
- public func reauthenticate(with provider: FederatedAuthProvider,
- uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
- return try await withCheckedThrowingContinuation { continuation in
- self.reauthenticate(with: provider, uiDelegate: uiDelegate) { result, error in
- if let result {
- continuation.resume(returning: result)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- #endif
- /** @fn getIDTokenWithCompletion:
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param completion Optionally; the block invoked when the token is available. Invoked
- asynchronously on the main thread in the future.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @objc(getIDTokenWithCompletion:)
- public func getIDToken(completion: ((String?, Error?) -> Void)?) {
- // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to
- // global work queue here.
- getIDTokenForcingRefresh(false, completion: completion)
- }
- /** @fn getIDTokenForcingRefresh:completion:
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
- other than an expiration.
- @param completion Optionally; the block invoked when the token is available. Invoked
- asynchronously on the main thread in the future.
- @remarks The authentication token will be refreshed (by making a network request) if it has
- expired, or if `forceRefresh` is true.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @objc(getIDTokenForcingRefresh:completion:)
- public func getIDTokenForcingRefresh(_ forceRefresh: Bool,
- completion: ((String?, Error?) -> Void)?) {
- getIDTokenResult(forcingRefresh: forceRefresh) { tokenResult, error in
- if let completion {
- DispatchQueue.main.async {
- completion(tokenResult?.token, error)
- }
- }
- }
- }
- /** @fn getIDTokenForcingRefresh:completion:
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
- other than an expiration.
- @returns The Token.
- @remarks The authentication token will be refreshed (by making a network request) if it has
- expired, or if `forceRefresh` is true.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func getIDToken(forcingRefresh forceRefresh: Bool = false) async throws -> String {
- return try await withCheckedThrowingContinuation { continuation in
- self.getIDTokenForcingRefresh(forceRefresh) { tokenResult, error in
- if let tokenResult {
- continuation.resume(returning: tokenResult)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- /** @fn getIDTokenResultWithCompletion:
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param completion Optionally; the block invoked when the token is available. Invoked
- asynchronously on the main thread in the future.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @objc(getIDTokenResultWithCompletion:)
- public func getIDTokenResult(completion: ((AuthTokenResult?, Error?) -> Void)?) {
- getIDTokenResult(forcingRefresh: false) { tokenResult, error in
- if let completion {
- DispatchQueue.main.async {
- completion(tokenResult, error)
- }
- }
- }
- }
- /** @fn getIDTokenResultForcingRefresh:completion:
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
- other than an expiration.
- @param completion Optionally; the block invoked when the token is available. Invoked
- asynchronously on the main thread in the future.
- @remarks The authentication token will be refreshed (by making a network request) if it has
- expired, or if `forceRefresh` is YES.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @objc(getIDTokenResultForcingRefresh:completion:)
- public func getIDTokenResult(forcingRefresh: Bool,
- completion: ((AuthTokenResult?, Error?) -> Void)?) {
- kAuthGlobalWorkQueue.async {
- self.internalGetToken(forceRefresh: forcingRefresh) { token, error in
- var tokenResult: AuthTokenResult?
- if let token {
- tokenResult = AuthTokenResult.tokenResult(token: token)
- AuthLog.logDebug(code: "I-AUT000017", message: "Actual token expiration date: " +
- "\(String(describing: tokenResult?.expirationDate))," +
- "current date: \(Date())")
- }
- if let completion {
- DispatchQueue.main.async {
- completion(tokenResult, error)
- }
- }
- }
- }
- }
- /** @fn getIDTokenResultForcingRefresh
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
- other than an expiration.
- @returns The token.
- @remarks The authentication token will be refreshed (by making a network request) if it has
- expired, or if `forceRefresh` is YES.
- @remarks See `AuthErrors` for a list of error codes that are common to all API methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func getIDTokenResult(forcingRefresh forceRefresh: Bool = false) async throws
- -> AuthTokenResult {
- return try await withCheckedThrowingContinuation { continuation in
- self.getIDTokenResult(forcingRefresh: forceRefresh) { tokenResult, error in
- if let tokenResult {
- continuation.resume(returning: tokenResult)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- /** @fn linkWithCredential:completion:
- @brief Associates a user account from a third-party identity provider with this user and
- returns additional identity provider data.
- @param credential The credential for the identity provider.
- @param completion Optionally; the block invoked when the unlinking is complete, or fails.
- Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a
- type already linked to this account.
- + `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a
- credential that has already been linked with a different Firebase account.
- + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity
- provider represented by the credential are not enabled. Enable them in the Auth section
- of the Firebase console.
- @remarks This method may also return error codes associated with `updateEmail(to:)` and
- `updatePassword(to:)` on `User`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc(linkWithCredential:completion:)
- public func link(with credential: AuthCredential,
- completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- if self.providerDataRaw[credential.provider] != nil {
- User.callInMainThreadWithAuthDataResultAndError(
- callback: completion,
- result: nil,
- error: AuthErrorUtils.providerAlreadyLinkedError()
- )
- return
- }
- if let emailCredential = credential as? EmailAuthCredential {
- self.link(withEmailCredential: emailCredential, completion: completion)
- return
- }
- #if !os(watchOS)
- if let gameCenterCredential = credential as? GameCenterAuthCredential {
- self.link(withGameCenterCredential: gameCenterCredential, completion: completion)
- return
- }
- #endif
- #if os(iOS)
- if let phoneCredential = credential as? PhoneAuthCredential {
- self.link(withPhoneCredential: phoneCredential, completion: completion)
- return
- }
- #endif
- self.taskQueue.enqueueTask { complete in
- let completeWithError = { result, error in
- complete()
- User.callInMainThreadWithAuthDataResultAndError(callback: completion, result: result,
- error: error)
- }
- self.internalGetToken { accessToken, error in
- if let error {
- completeWithError(nil, error)
- return
- }
- guard let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Error: Unexpected nil requestConfiguration.")
- }
- let request = VerifyAssertionRequest(providerID: credential.provider,
- requestConfiguration: requestConfiguration)
- credential.prepare(request)
- request.accessToken = accessToken
- AuthBackend.post(withRequest: request) { rawResponse, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- completeWithError(nil, error)
- return
- }
- guard let response = rawResponse as? VerifyAssertionResponse else {
- fatalError("Internal Auth Error: response type is not an VerifyAssertionResponse")
- }
- let additionalUserInfo = AdditionalUserInfo.userInfo(verifyAssertionResponse: response)
- let updatedOAuthCredential = OAuthCredential(withVerifyAssertionResponse: response)
- let result = AuthDataResult(withUser: self, additionalUserInfo: additionalUserInfo,
- credential: updatedOAuthCredential)
- guard let idToken = response.idToken,
- let refreshToken = response.refreshToken else {
- fatalError("Internal Auth Error: missing token in EmailLinkSignInResponse")
- }
- self.updateTokenAndRefreshUser(idToken: idToken,
- refreshToken: refreshToken,
- accessToken: accessToken,
- expirationDate: response.approximateExpirationDate,
- result: result,
- requestConfiguration: requestConfiguration,
- completion: completion,
- withTaskComplete: complete)
- }
- }
- }
- }
- }
- /** @fn linkWithCredential:
- @brief Associates a user account from a third-party identity provider with this user and
- returns additional identity provider data.
- @param credential The credential for the identity provider.
- @returns The AuthDataResult.
- @remarks Possible error codes:
- + `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a
- type already linked to this account.
- + `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a
- credential that has already been linked with a different Firebase account.
- + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity
- provider represented by the credential are not enabled. Enable them in the Auth section
- of the Firebase console.
- @remarks This method may also return error codes associated with `updateEmail(to:)` and
- `updatePassword(to:)` on `User`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- @discardableResult
- public func link(with credential: AuthCredential) async throws -> AuthDataResult {
- return try await withCheckedThrowingContinuation { continuation in
- self.link(with: credential) { result, error in
- if let result {
- continuation.resume(returning: result)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- #if os(iOS)
- /** @fn linkWithProvider:UIDelegate:completion:
- @brief link the user with the provided auth provider instance.
- This method is available on iOSonly.
- @param provider An instance of an auth provider used to initiate the link flow.
- @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate`
- protocol used for presenting the web context. If nil, a default `AuthUIDelegate`
- will be used.
- @param completion Optionally; a block which is invoked when the link flow finishes, or
- is canceled. Invoked asynchronously on the main thread in the future.
- */
- @objc(linkWithProvider:UIDelegate:completion:)
- public func link(with provider: FederatedAuthProvider,
- uiDelegate: AuthUIDelegate?,
- completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- provider.getCredentialWith(uiDelegate) { credential, error in
- if let error {
- if let completion {
- completion(nil, error)
- }
- } else {
- guard let credential else {
- fatalError("Failed to get credential for link withProvider")
- }
- self.link(with: credential, completion: completion)
- }
- }
- }
- }
- /** @fn linkWithProvider:UIDelegate:
- @brief link the user with the provided auth provider instance.
- This method is available on iOS, macOS Catalyst, and tvOS only.
- @param provider An instance of an auth provider used to initiate the link flow.
- @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate`
- protocol used for presenting the web context. If nil, a default `AuthUIDelegate`
- will be used.
- @returns An AuthDataResult.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- @discardableResult
- public func link(with provider: FederatedAuthProvider,
- uiDelegate: AuthUIDelegate?) async throws -> AuthDataResult {
- return try await withCheckedThrowingContinuation { continuation in
- self.link(with: provider, uiDelegate: uiDelegate) { result, error in
- if let result {
- continuation.resume(returning: result)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- #endif
- /** @fn unlinkFromProvider:completion:
- @brief Disassociates a user account from a third-party identity provider with this user.
- @param provider The provider ID of the provider to unlink.
- @param completion Optionally; the block invoked when the unlinking is complete, or fails.
- Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider
- that is not linked to the account.
- + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
- operation that requires a recent login from the user. This error indicates the user
- has not signed in recently enough. To resolve, reauthenticate the user by calling
- `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc public func unlink(fromProvider provider: String,
- completion: ((User?, Error?) -> Void)? = nil) {
- taskQueue.enqueueTask { complete in
- let completeAndCallbackWithError = { error in
- complete()
- User.callInMainThreadWithUserAndError(callback: completion, user: self,
- error: error)
- }
- self.internalGetToken { accessToken, error in
- if let error {
- completeAndCallbackWithError(error)
- return
- }
- guard let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Error: Unexpected nil requestConfiguration.")
- }
- let request = SetAccountInfoRequest(requestConfiguration: requestConfiguration)
- request.accessToken = accessToken
- if self.providerDataRaw[provider] == nil {
- completeAndCallbackWithError(AuthErrorUtils.noSuchProviderError())
- return
- }
- request.deleteProviders = [provider]
- AuthBackend.post(withRequest: request) { rawResponse, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- completeAndCallbackWithError(error)
- return
- }
- // We can't just use the provider info objects in FIRSetAccountInfoResponse
- // because they don't have localID and email fields. Remove the specific
- // provider manually.
- self.providerDataRaw.removeValue(forKey: provider)
- if provider == EmailAuthProvider.id {
- self.hasEmailPasswordCredential = false
- }
- #if os(iOS)
- // After successfully unlinking a phone auth provider, remove the phone number
- // from the cached user info.
- if provider == PhoneAuthProvider.id {
- self.phoneNumber = nil
- }
- #endif
- if let response = rawResponse as? SetAccountInfoResponse,
- let idToken = response.idToken,
- let refreshToken = response.refreshToken {
- let tokenService = SecureTokenService(withRequestConfiguration: requestConfiguration,
- accessToken: idToken,
- accessTokenExpirationDate: response
- .approximateExpirationDate,
- refreshToken: refreshToken)
- self.setTokenService(tokenService: tokenService) { error in
- completeAndCallbackWithError(error)
- }
- return
- }
- if let error = self.updateKeychain() {
- completeAndCallbackWithError(error)
- return
- }
- completeAndCallbackWithError(nil)
- }
- }
- }
- }
- /** @fn unlinkFromProvider:
- @brief Disassociates a user account from a third-party identity provider with this user.
- @param provider The provider ID of the provider to unlink.
- @returns The user.
- @remarks Possible error codes:
- + `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider
- that is not linked to the account.
- + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
- operation that requires a recent login from the user. This error indicates the user
- has not signed in recently enough. To resolve, reauthenticate the user by calling
- `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func unlink(fromProvider provider: String) async throws -> User {
- return try await withCheckedThrowingContinuation { continuation in
- self.unlink(fromProvider: provider) { result, error in
- if let result {
- continuation.resume(returning: result)
- } else if let error {
- continuation.resume(throwing: error)
- }
- }
- }
- }
- /** @fn sendEmailVerificationWithCompletion:
- @brief Initiates email verification for the user.
- @param completion Optionally; the block invoked when the request to send an email verification
- is complete, or fails. Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
- sent in the request.
- + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
- the console for this action.
- + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
- sending update email.
- + `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc(sendEmailVerificationWithCompletion:)
- public func __sendEmailVerification(withCompletion completion: ((Error?) -> Void)?) {
- sendEmailVerification(withCompletion: completion)
- }
- /** @fn sendEmailVerificationWithActionCodeSettings:completion:
- @brief Initiates email verification for the user.
- @param actionCodeSettings An `ActionCodeSettings` object containing settings related to
- handling action codes.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
- sent in the request.
- + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
- the console for this action.
- + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
- sending update email.
- + `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
- + `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when
- a iOS App Store ID is provided.
- + `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name
- is missing when the `androidInstallApp` flag is set to true.
- + `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the
- continue URL is not allowlisted in the Firebase console.
- + `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the
- continue URL is not valid.
- */
- @objc(sendEmailVerificationWithActionCodeSettings:completion:)
- public func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil,
- withCompletion completion: ((Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.internalGetToken { accessToken, error in
- if let error {
- User.callInMainThreadWithError(callback: completion, error: error)
- return
- }
- guard let accessToken else {
- fatalError("Internal Error: Both error and accessToken are nil.")
- }
- guard let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Error: Unexpected nil requestConfiguration.")
- }
- let request = GetOOBConfirmationCodeRequest.verifyEmailRequest(
- accessToken: accessToken,
- actionCodeSettings: actionCodeSettings,
- requestConfiguration: requestConfiguration
- )
- AuthBackend.post(withRequest: request) { response, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- }
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- }
- /** @fn sendEmailVerificationWithActionCodeSettings:
- @brief Initiates email verification for the user.
- @param actionCodeSettings An `ActionCodeSettings` object containing settings related to
- handling action codes.
- @remarks Possible error codes:
- + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was
- sent in the request.
- + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in
- the console for this action.
- + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for
- sending update email.
- + `AuthErrorCodeUserNotFound` - Indicates the user account was not found.
- + `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when
- a iOS App Store ID is provided.
- + `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name
- is missing when the `androidInstallApp` flag is set to true.
- + `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the
- continue URL is not allowlisted in the Firebase console.
- + `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the
- continue URL is not valid.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil) async throws
- {
- return try await withCheckedThrowingContinuation { continuation in
- self.sendEmailVerification(with: actionCodeSettings) { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- /** @fn deleteWithCompletion:
- @brief Deletes the user account (also signs out the user, if this was the current user).
- @param completion Optionally; the block invoked when the request to delete the account is
- complete, or fails. Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
- operation that requires a recent login from the user. This error indicates the user
- has not signed in recently enough. To resolve, reauthenticate the user by calling
- `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @objc public func delete(withCompletion completion: ((Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.internalGetToken { accessToken, error in
- if let error {
- User.callInMainThreadWithError(callback: completion, error: error)
- return
- }
- guard let accessToken else {
- fatalError("Internal Error: Both error and accessToken are nil.")
- }
- guard let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Error: Unexpected nil requestConfiguration.")
- }
- let request = DeleteAccountRequest(localID: self.uid, accessToken: accessToken,
- requestConfiguration: requestConfiguration)
- AuthBackend.post(withRequest: request) { response, error in
- if let error {
- User.callInMainThreadWithError(callback: completion, error: error)
- return
- }
- do {
- try self.auth?.signOutByForce(withUserID: self.uid)
- } catch {
- User.callInMainThreadWithError(callback: completion, error: error)
- return
- }
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- }
- /** @fn delete
- @brief Deletes the user account (also signs out the user, if this was the current user).
- @param completion Optionally; the block invoked when the request to delete the account is
- complete, or fails. Invoked asynchronously on the main thread in the future.
- @remarks Possible error codes:
- + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive
- operation that requires a recent login from the user. This error indicates the user
- has not signed in recently enough. To resolve, reauthenticate the user by calling
- `reauthenticate(with:)`.
- @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func delete() async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.delete { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- /** @fn sendEmailVerificationBeforeUpdatingEmail:completion:
- @brief Send an email to verify the ownership of the account then update to the new email.
- @param email The email to be updated to.
- @param completion Optionally; the block invoked when the request to send the verification
- email is complete, or fails.
- */
- @objc(sendEmailVerificationBeforeUpdatingEmail:completion:)
- public func __sendEmailVerificationBeforeUpdating(email: String,
- completion: ((Error?) -> Void)?) {
- sendEmailVerification(beforeUpdatingEmail: email, completion: completion)
- }
- /** @fn sendEmailVerificationBeforeUpdatingEmail:completion:
- @brief Send an email to verify the ownership of the account then update to the new email.
- @param email The email to be updated to.
- @param actionCodeSettings An `ActionCodeSettings` object containing settings related to
- handling action codes.
- @param completion Optionally; the block invoked when the request to send the verification
- email is complete, or fails.
- */
- @objc public func sendEmailVerification(beforeUpdatingEmail email: String,
- actionCodeSettings: ActionCodeSettings? =
- nil,
- completion: ((Error?) -> Void)? = nil) {
- kAuthGlobalWorkQueue.async {
- self.internalGetToken { accessToken, error in
- if let error {
- User.callInMainThreadWithError(callback: completion, error: error)
- return
- }
- guard let accessToken else {
- fatalError("Internal Error: Both error and accessToken are nil.")
- }
- guard let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Error: Unexpected nil requestConfiguration.")
- }
- let request = GetOOBConfirmationCodeRequest.verifyBeforeUpdateEmail(
- accessToken: accessToken,
- newEmail: email,
- actionCodeSettings: actionCodeSettings,
- requestConfiguration: requestConfiguration
- )
- AuthBackend.post(withRequest: request) { response, error in
- User.callInMainThreadWithError(callback: completion, error: error)
- }
- }
- }
- }
- /** @fn sendEmailVerificationBeforeUpdatingEmail:completion:
- @brief Send an email to verify the ownership of the account then update to the new email.
- @param email The email to be updated to.
- @param actionCodeSettings An `ActionCodeSettings` object containing settings related to
- handling action codes.
- @throws on failure.
- */
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- public func sendEmailVerification(beforeUpdatingEmail newEmail: String,
- actionCodeSettings: ActionCodeSettings? = nil) async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.sendEmailVerification(beforeUpdatingEmail: newEmail,
- actionCodeSettings: actionCodeSettings) { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- @objc public func rawAccessToken() -> String {
- return tokenService.accessToken
- }
- @objc public func accessTokenExpirationDate() -> Date? {
- return tokenService.accessTokenExpirationDate
- }
- // MARK: Internal implementations below
- init(withTokenService tokenService: SecureTokenService) {
- providerDataRaw = [:]
- taskQueue = AuthSerialTaskQueue()
- self.tokenService = tokenService
- isAnonymous = false
- isEmailVerified = false
- metadata = UserMetadata(withCreationDate: nil, lastSignInDate: nil)
- tenantID = nil
- #if os(iOS)
- multiFactor = MultiFactor(withMFAEnrollments: [])
- #endif
- uid = ""
- hasEmailPasswordCredential = false
- requestConfiguration = AuthRequestConfiguration(apiKey: "", appID: "")
- }
- // TODO: internal Swift
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- @objc public class func retrieveUser(withAuth auth: Auth,
- accessToken: String?,
- accessTokenExpirationDate: Date?,
- refreshToken: String?,
- anonymous: Bool,
- callback: @escaping (User?, Error?) -> Void) {
- guard let accessToken = accessToken,
- let refreshToken = refreshToken else {
- fatalError("Internal FirebaseAuth Error: nil token")
- }
- let tokenService = SecureTokenService(withRequestConfiguration: auth.requestConfiguration,
- accessToken: accessToken,
- accessTokenExpirationDate: accessTokenExpirationDate,
- refreshToken: refreshToken)
- let user = User(withTokenService: tokenService)
- user.auth = auth
- user.tenantID = auth.tenantID
- user.requestConfiguration = auth.requestConfiguration
- user.internalGetToken { accessToken, error in
- if let error {
- callback(nil, error)
- return
- }
- guard let accessToken else {
- fatalError("Internal FirebaseAuthError: Both error and accessToken are nil")
- }
- let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken,
- requestConfiguration: user
- .requestConfiguration)
- AuthBackend.post(withRequest: getAccountInfoRequest) { rawResponse, error in
- if let error {
- // No need to sign out user here for errors because the user hasn't been signed in yet.
- callback(nil, error)
- return
- }
- guard let response = rawResponse as? GetAccountInfoResponse else {
- fatalError("Internal FirebaseAuthError: Response should be a GetAccountInfoResponse")
- }
- user.isAnonymous = anonymous
- user.update(withGetAccountInfoResponse: response)
- callback(user, nil)
- }
- }
- }
- @objc public var providerID: String {
- return "Firebase"
- }
- /** @property uid
- @brief The provider's user ID for the user.
- */
- @objc public var uid: String
- /** @property displayName
- @brief The name of the user.
- */
- @objc public var displayName: String?
- /** @property photoURL
- @brief The URL of the user's profile photo.
- */
- @objc public var photoURL: URL?
- /** @property email
- @brief The user's email address.
- */
- @objc public var email: String?
- /** @property phoneNumber
- @brief A phone number associated with the user.
- @remarks This property is only available for users authenticated via phone number auth.
- */
- @objc public var phoneNumber: String?
- /** @var hasEmailPasswordCredential
- @brief Whether or not the user can be authenticated by using Firebase email and password.
- */
- private var hasEmailPasswordCredential: Bool
- /** @var _taskQueue
- @brief Used to serialize the update profile calls.
- */
- private var taskQueue: AuthSerialTaskQueue
- /** @property requestConfiguration
- @brief A strong reference to a requestConfiguration instance associated with this user instance.
- */
- // TODO: internal
- @objc public var requestConfiguration: AuthRequestConfiguration
- /** @var _tokenService
- @brief A secure token service associated with this user. For performing token exchanges and
- refreshing access tokens.
- */
- // TODO: internal
- @objc public var tokenService: SecureTokenService
- /** @property auth
- @brief A weak reference to a FIRAuth instance associated with this instance.
- */
- // TODO: internal
- @objc public weak var auth: Auth?
- // MARK: Private functions
- private func updateEmail(email: String?,
- password: String?,
- callback: @escaping (Error?) -> Void) {
- let hadEmailPasswordCredential = hasEmailPasswordCredential
- executeUserUpdateWithChanges(changeBlock: { user, request in
- if let email {
- request.email = email
- }
- if let password {
- request.password = password
- }
- }) { error in
- if let error {
- callback(error)
- return
- }
- if let email {
- self.email = email
- }
- if self.email != nil {
- if !hadEmailPasswordCredential {
- // The list of providers need to be updated for the newly added email-password provider.
- self.internalGetToken { accessToken, error in
- if let error {
- callback(error)
- return
- }
- guard let accessToken else {
- fatalError("Auth Internal Error: Both accessToken and error are nil")
- }
- if let requestConfiguration = self.auth?.requestConfiguration {
- let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken,
- requestConfiguration: requestConfiguration)
- AuthBackend.post(withRequest: getAccountInfoRequest) { response, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- callback(error)
- return
- }
- guard let accountInfoResponse = response as? GetAccountInfoResponse else {
- fatalError("Auth Internal Error: Response is not an GetAccountInfoResponse")
- }
- if let users = accountInfoResponse.users {
- for userAccountInfo in users {
- // Set the account to non-anonymous if there are any providers, even if
- // they're not email/password ones.
- if let providerUsers = userAccountInfo.providerUserInfo {
- if providerUsers.count > 0 {
- self.isAnonymous = false
- for providerUserInfo in providerUsers {
- if providerUserInfo.providerID == EmailAuthProvider.id {
- self.hasEmailPasswordCredential = true
- break
- }
- }
- }
- }
- }
- }
- self.update(withGetAccountInfoResponse: accountInfoResponse)
- if let error = self.updateKeychain() {
- callback(error)
- return
- }
- callback(nil)
- }
- }
- }
- return
- }
- }
- if let error = self.updateKeychain() {
- callback(error)
- return
- }
- callback(nil)
- }
- }
- /** @fn executeUserUpdateWithChanges:callback:
- @brief Performs a setAccountInfo request by mutating the results of a getAccountInfo response,
- atomically in regards to other calls to this method.
- @param changeBlock A block responsible for mutating a template @c FIRSetAccountInfoRequest
- @param callback A block to invoke when the change is complete. Invoked asynchronously on the
- auth global work queue in the future.
- */
- func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponseUser,
- SetAccountInfoRequest) -> Void,
- callback: @escaping (Error?) -> Void) {
- taskQueue.enqueueTask { complete in
- self.getAccountInfoRefreshingCache { user, error in
- if let error {
- complete()
- callback(error)
- return
- }
- guard let user else {
- fatalError("Internal error: Both user and error are nil")
- }
- self.internalGetToken { accessToken, error in
- if let error {
- complete()
- callback(error)
- return
- }
- if let configuration = self.auth?.requestConfiguration {
- // Mutate setAccountInfoRequest in block
- let setAccountInfoRequest = SetAccountInfoRequest(requestConfiguration: configuration)
- setAccountInfoRequest.accessToken = accessToken
- changeBlock(user, setAccountInfoRequest)
- // Execute request:
- AuthBackend.post(withRequest: setAccountInfoRequest) { response, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- complete()
- callback(error)
- return
- }
- if let accountInfoResponse = response as? SetAccountInfoResponse {
- if let idToken = accountInfoResponse.idToken,
- let refreshToken = accountInfoResponse.refreshToken {
- let tokenService = SecureTokenService(
- withRequestConfiguration: configuration,
- accessToken: idToken,
- accessTokenExpirationDate: accountInfoResponse.approximateExpirationDate,
- refreshToken: refreshToken
- )
- self.setTokenService(tokenService: tokenService) { error in
- complete()
- callback(error)
- }
- return
- }
- }
- complete()
- callback(nil)
- }
- }
- }
- }
- }
- }
- /** @fn setTokenService:callback:
- @brief Sets a new token service for the @c FIRUser instance.
- @param tokenService The new token service object.
- @param callback The block to be called in the global auth working queue once finished.
- @remarks The method makes sure the token service has access and refresh token and the new tokens
- are saved in the keychain before calling back.
- */
- private func setTokenService(tokenService: SecureTokenService,
- callback: @escaping (Error?) -> Void) {
- tokenService.fetchAccessToken(forcingRefresh: false) { token, error, tokenUpdated in
- if let error {
- callback(error)
- return
- }
- self.tokenService = tokenService
- if let error = self.updateKeychain() {
- callback(error)
- return
- }
- callback(nil)
- }
- }
- /** @fn getAccountInfoRefreshingCache:
- @brief Gets the users's account data from the server, updating our local values.
- @param callback Invoked when the request to getAccountInfo has completed, or when an error has
- been detected. Invoked asynchronously on the auth global work queue in the future.
- */
- private func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponseUser?,
- Error?) -> Void) {
- internalGetToken { token, error in
- if let error {
- callback(nil, error)
- return
- }
- guard let token else {
- fatalError("Internal Error: Both error and token are nil.")
- }
- guard let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Error: Unexpected nil requestConfiguration.")
- }
- let request = GetAccountInfoRequest(accessToken: token,
- requestConfiguration: requestConfiguration)
- AuthBackend.post(withRequest: request) { response, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- callback(nil, error)
- return
- }
- guard let accountInfoResponse = response as? GetAccountInfoResponse else {
- fatalError("Internal Error: wrong response type")
- }
- self.update(withGetAccountInfoResponse: accountInfoResponse)
- if let error = self.updateKeychain() {
- callback(nil, error)
- return
- }
- callback(accountInfoResponse.users?.first, nil)
- }
- }
- }
- private func update(withGetAccountInfoResponse response: GetAccountInfoResponse) {
- guard let user = response.users?.first else {
- // Silent fallthrough in ObjC code.
- AuthLog.logWarning(code: "I-AUT000016", message: "Missing user in GetAccountInfoResponse")
- return
- }
- uid = user.localID ?? ""
- email = user.email
- isEmailVerified = user.emailVerified
- displayName = user.displayName
- photoURL = user.photoURL
- phoneNumber = user.phoneNumber
- hasEmailPasswordCredential = user.passwordHash != nil && user.passwordHash!.count > 0
- metadata = UserMetadata(withCreationDate: user.creationDate,
- lastSignInDate: user.lastLoginDate)
- var providerData: [String: UserInfoImpl] = [:]
- if let providerUserInfos = user.providerUserInfo {
- for providerUserInfo in providerUserInfos {
- let userInfo = UserInfoImpl.userInfo(withGetAccountInfoResponseProviderUserInfo:
- providerUserInfo)
- if let providerID = providerUserInfo.providerID {
- providerData[providerID] = userInfo
- }
- }
- }
- providerDataRaw = providerData
- #if os(iOS)
- if let enrollments = user.mfaEnrollments {
- multiFactor = MultiFactor(withMFAEnrollments: enrollments)
- }
- multiFactor.user = self
- #endif
- }
- #if os(iOS)
- /** @fn internalUpdateOrLinkPhoneNumber
- @brief Updates the phone number for the user. On success, the cached user profile data is
- updated.
- @param phoneAuthCredential The new phone number credential corresponding to the phone number
- to be added to the Firebase account, if a phone number is already linked to the account this
- new phone number will replace it.
- @param isLinkOperation Boolean value indicating whether or not this is a link operation.
- @param completion Optionally; the block invoked when the user profile change has finished.
- Invoked asynchronously on the global work queue in the future.
- */
- private func internalUpdateOrLinkPhoneNumber(credential: PhoneAuthCredential,
- isLinkOperation: Bool,
- completion: @escaping (Error?) -> Void) {
- internalGetToken { accessToken, error in
- if let error {
- completion(error)
- return
- }
- guard let accessToken = accessToken else {
- fatalError("Auth Internal Error: Both accessToken and error are nil")
- }
- guard let configuration = self.auth?.requestConfiguration else {
- fatalError("Auth Internal Error: nil value for VerifyPhoneNumberRequest initializer")
- }
- switch credential.credentialKind {
- case .phoneNumber: fatalError("Internal Error: Missing verificationCode")
- case let .verification(verificationID, code):
- let operation = isLinkOperation ? AuthOperationType.link : AuthOperationType.update
- let request = VerifyPhoneNumberRequest(verificationID: verificationID,
- verificationCode: code,
- operation: operation,
- requestConfiguration: configuration)
- request.accessToken = accessToken
- AuthBackend.post(withRequest: request) { response, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- completion(error)
- return
- }
- // Update the new token and refresh user info again.
- if let verifyResponse = response as? VerifyPhoneNumberResponse {
- if let idToken = verifyResponse.idToken,
- let refreshToken = verifyResponse.refreshToken {
- self.tokenService = SecureTokenService(
- withRequestConfiguration: configuration,
- accessToken: idToken,
- accessTokenExpirationDate: verifyResponse.approximateExpirationDate,
- refreshToken: refreshToken
- )
- }
- }
- // Get account info to update cached user info.
- self.getAccountInfoRefreshingCache { user, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- completion(error)
- return
- }
- self.isAnonymous = false
- if let error = self.updateKeychain() {
- completion(error)
- return
- }
- completion(nil)
- }
- }
- }
- }
- }
- #endif
- private func link(withEmailCredential emailCredential: EmailAuthCredential,
- completion: ((AuthDataResult?, Error?) -> Void)?) {
- if hasEmailPasswordCredential {
- User.callInMainThreadWithAuthDataResultAndError(
- callback: completion,
- result: nil,
- error: AuthErrorUtils
- .providerAlreadyLinkedError()
- )
- return
- }
- switch emailCredential.emailType {
- case let .password(password):
- updateEmail(email: emailCredential.email, password: password) { error in
- if let error {
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- result: nil,
- error: error)
- } else {
- let result = AuthDataResult(withUser: self, additionalUserInfo: nil)
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- result: result,
- error: nil)
- }
- }
- case let .link(link):
- internalGetToken { accessToken, error in
- var queryItems = AuthWebUtils.parseURL(link)
- if link.count == 0 {
- if let urlComponents = URLComponents(string: link),
- let query = urlComponents.query {
- queryItems = AuthWebUtils.parseURL(query)
- }
- }
- guard let actionCode = queryItems["oobCode"],
- let requestConfiguration = self.auth?.requestConfiguration else {
- fatalError("Internal Auth Error: Missing oobCode or requestConfiguration")
- }
- let request = EmailLinkSignInRequest(email: emailCredential.email,
- oobCode: actionCode,
- requestConfiguration: requestConfiguration)
- request.idToken = accessToken
- AuthBackend.post(withRequest: request) { rawResponse, error in
- if let error {
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- result: nil,
- error: error)
- return
- }
- guard let response = rawResponse as? EmailLinkSignInResponse else {
- fatalError("Internal Auth Error: response type is not an EmailLinkSignInResponse")
- }
- guard let idToken = response.idToken,
- let refreshToken = response.refreshToken else {
- fatalError("Internal Auth Error: missing token in EmailLinkSignInResponse")
- }
- self.updateTokenAndRefreshUser(idToken: idToken,
- refreshToken: refreshToken,
- accessToken: accessToken,
- expirationDate: response.approximateExpirationDate,
- result: AuthDataResult(
- withUser: self,
- additionalUserInfo: nil
- ),
- requestConfiguration: requestConfiguration,
- completion: completion)
- }
- }
- }
- }
- #if !os(watchOS)
- private func link(withGameCenterCredential gameCenterCredential: GameCenterAuthCredential,
- completion: ((AuthDataResult?, Error?) -> Void)?) {
- internalGetToken { accessToken, error in
- guard let requestConfiguration = self.auth?.requestConfiguration,
- let publicKeyURL = gameCenterCredential.publicKeyURL,
- let signature = gameCenterCredential.signature,
- let salt = gameCenterCredential.salt else {
- fatalError("Internal Auth Error: Nil value field for SignInWithGameCenterRequest")
- }
- let request = SignInWithGameCenterRequest(teamPlayerID: gameCenterCredential.teamPlayerID,
- gamePlayerID: gameCenterCredential.gamePlayerID,
- publicKeyURL: publicKeyURL,
- signature: signature,
- salt: salt,
- timestamp: gameCenterCredential.timestamp,
- displayName: gameCenterCredential.displayName,
- requestConfiguration: requestConfiguration)
- request.accessToken = accessToken
- AuthBackend.post(withRequest: request) { rawResponse, error in
- if let error {
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- result: nil,
- error: error)
- return
- }
- guard let response = rawResponse as? SignInWithGameCenterResponse else {
- fatalError("Internal Auth Error: response type is not an SignInWithGameCenterResponse")
- }
- guard let idToken = response.idToken,
- let refreshToken = response.refreshToken else {
- fatalError("Internal Auth Error: missing token in EmailLinkSignInResponse")
- }
- self.updateTokenAndRefreshUser(idToken: idToken,
- refreshToken: refreshToken,
- accessToken: accessToken,
- expirationDate: response.approximateExpirationDate,
- result: AuthDataResult(
- withUser: self,
- additionalUserInfo: nil
- ),
- requestConfiguration: requestConfiguration,
- completion: completion)
- }
- }
- }
- #endif
- #if os(iOS)
- private func link(withPhoneCredential phoneCredential: PhoneAuthCredential,
- completion: ((AuthDataResult?, Error?) -> Void)?) {
- internalUpdateOrLinkPhoneNumber(credential: phoneCredential,
- isLinkOperation: true) { error in
- if let error {
- User.callInMainThreadWithAuthDataResultAndError(
- callback: completion,
- result: nil,
- error: error
- )
- } else {
- let result = AuthDataResult(withUser: self, additionalUserInfo: nil)
- User.callInMainThreadWithAuthDataResultAndError(
- callback: completion,
- result: result,
- error: nil
- )
- }
- }
- }
- #endif
- // Update the new token and refresh user info again.
- private func updateTokenAndRefreshUser(idToken: String, refreshToken: String,
- accessToken: String?,
- expirationDate: Date?,
- result: AuthDataResult,
- requestConfiguration: AuthRequestConfiguration,
- completion: ((AuthDataResult?, Error?) -> Void)?,
- withTaskComplete complete: FIRAuthSerialTaskCompletionBlock? =
- nil) {
- tokenService = SecureTokenService(
- withRequestConfiguration: requestConfiguration,
- accessToken: idToken,
- accessTokenExpirationDate: expirationDate,
- refreshToken: refreshToken
- )
- internalGetToken { response, error in
- if let error {
- User.callInMainThreadWithAuthDataResultAndError(callback: completion,
- complete: complete,
- error: error)
- return
- }
- guard let accessToken else {
- fatalError("Internal Auth Error: nil access Token")
- }
- let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken,
- requestConfiguration: requestConfiguration)
- AuthBackend.post(withRequest: getAccountInfoRequest) { rawResponse, error in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- User.callInMainThreadWithAuthDataResultAndError(callback: completion, error: error)
- return
- }
- guard let response = rawResponse as? GetAccountInfoResponse else {
- fatalError("Internal Auth Error: response is not a GetAccountInfoResponse")
- }
- self.isAnonymous = false
- self.update(withGetAccountInfoResponse: response)
- if let error = self.updateKeychain() {
- User.callInMainThreadWithAuthDataResultAndError(callback: completion, complete: complete,
- error: error)
- return
- }
- User.callInMainThreadWithAuthDataResultAndError(callback: completion, complete: complete,
- result: result)
- }
- }
- }
- /** @fn signOutIfTokenIsInvalidWithError:
- @brief Signs out this user if the user or the token is invalid.
- @param error The error from the server.
- */
- private func signOutIfTokenIsInvalid(withError error: Error) {
- let code = (error as NSError).code
- if code == AuthErrorCode.userNotFound.rawValue ||
- code == AuthErrorCode.userDisabled.rawValue ||
- code == AuthErrorCode.invalidUserToken.rawValue ||
- code == AuthErrorCode.userTokenExpired.rawValue {
- AuthLog.logNotice(code: "I-AUT000016",
- message: "Invalid user token detected, user is automatically signed out.")
- try? auth?.signOutByForce(withUserID: uid)
- }
- }
- /** @fn internalGetToken
- @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
- @param callback The block to invoke when the token is available. Invoked asynchronously on the
- global work thread in the future.
- */
- // TODO: internal
- @objc(internalGetTokenForcingRefresh:callback:)
- public func internalGetToken(forceRefresh: Bool = false,
- callback: @escaping (String?, Error?) -> Void) {
- tokenService.fetchAccessToken(forcingRefresh: forceRefresh) { token, error, tokenUpdated in
- if let error {
- self.signOutIfTokenIsInvalid(withError: error)
- callback(nil, error)
- return
- }
- if tokenUpdated {
- if let error = self.updateKeychain() {
- callback(nil, error)
- return
- }
- }
- callback(token, nil)
- }
- }
- /** @fn updateKeychain:
- @brief Updates the keychain for user token or info changes.
- @param error The error if NO is returned.
- @return Whether the operation is successful.
- */
- func updateKeychain() -> Error? {
- return auth?.updateKeychain(withUser: self)
- }
- /** @fn callInMainThreadWithError
- @brief Calls a callback in main thread with error.
- @param callback The callback to be called in main thread.
- @param error The error to pass to callback.
- */
- internal class func callInMainThreadWithError(callback: ((Error?) -> Void)?, error: Error?) {
- if let callback {
- DispatchQueue.main.async {
- callback(error)
- }
- }
- }
- /** @fn callInMainThreadWithUserAndError
- @brief Calls a callback in main thread with user and error.
- @param callback The callback to be called in main thread.
- @param user The user to pass to callback if there is no error.
- @param error The error to pass to callback.
- */
- private class func callInMainThreadWithUserAndError(callback: ((User?, Error?) -> Void)?,
- user: User,
- error: Error?) {
- if let callback {
- DispatchQueue.main.async {
- callback((error != nil) ? nil : user, error)
- }
- }
- }
- /** @fn callInMainThreadWithAuthDataResultAndError
- @brief Calls a callback in main thread with user and error.
- @param callback The callback to be called in main thread.
- @param result The result to pass to callback if there is no error.
- @param error The error to pass to callback.
- */
- private class func callInMainThreadWithAuthDataResultAndError(callback: ((AuthDataResult?,
- Error?) -> Void)?,
- complete: FIRAuthSerialTaskCompletionBlock? =
- nil,
- result: AuthDataResult? = nil,
- error: Error? = nil) {
- if let callback {
- DispatchQueue.main.async {
- if let complete {
- complete()
- }
- callback(result, error)
- }
- }
- }
- // MARK: NSSecureCoding
- private let kUserIDCodingKey = "userID"
- private let kHasEmailPasswordCredentialCodingKey = "hasEmailPassword"
- private let kAnonymousCodingKey = "anonymous"
- private let kEmailCodingKey = "email"
- private let kPhoneNumberCodingKey = "phoneNumber"
- private let kEmailVerifiedCodingKey = "emailVerified"
- private let kDisplayNameCodingKey = "displayName"
- private let kPhotoURLCodingKey = "photoURL"
- private let kProviderDataKey = "providerData"
- private let kAPIKeyCodingKey = "APIKey"
- private let kFirebaseAppIDCodingKey = "firebaseAppID"
- private let kTokenServiceCodingKey = "tokenService"
- private let kMetadataCodingKey = "metadata"
- private let kMultiFactorCodingKey = "multiFactor"
- private let kTenantIDCodingKey = "tenantID"
- public static var supportsSecureCoding = true
- public func encode(with coder: NSCoder) {
- coder.encode(uid, forKey: kUserIDCodingKey)
- coder.encode(isAnonymous, forKey: kAnonymousCodingKey)
- coder.encode(hasEmailPasswordCredential, forKey: kHasEmailPasswordCredentialCodingKey)
- coder.encode(providerDataRaw, forKey: kProviderDataKey)
- coder.encode(email, forKey: kEmailCodingKey)
- coder.encode(phoneNumber, forKey: kPhoneNumberCodingKey)
- coder.encode(isEmailVerified, forKey: kEmailVerifiedCodingKey)
- coder.encode(photoURL, forKey: kPhotoURLCodingKey)
- coder.encode(displayName, forKey: kDisplayNameCodingKey)
- coder.encode(metadata, forKey: kMetadataCodingKey)
- coder.encode(tenantID, forKey: kTenantIDCodingKey)
- if let auth {
- coder.encode(auth.requestConfiguration.apiKey, forKey: kAPIKeyCodingKey)
- coder.encode(auth.requestConfiguration.appID, forKey: kFirebaseAppIDCodingKey)
- }
- coder.encode(tokenService, forKey: kTokenServiceCodingKey)
- #if os(iOS)
- coder.encode(multiFactor, forKey: kMultiFactorCodingKey)
- #endif
- }
- public required init?(coder: NSCoder) {
- guard let userID = coder.decodeObject(of: NSString.self, forKey: kUserIDCodingKey) as? String,
- let apiKey = coder.decodeObject(of: NSString.self, forKey: kAPIKeyCodingKey) as? String,
- let appID = coder.decodeObject(
- of: NSString.self,
- forKey: kFirebaseAppIDCodingKey
- ) as? String,
- let tokenService = coder.decodeObject(forKey: kTokenServiceCodingKey)
- as? SecureTokenService else {
- return nil
- }
- let anonymous = coder.decodeBool(forKey: kAnonymousCodingKey)
- let hasEmailPasswordCredential = coder.decodeBool(forKey: kHasEmailPasswordCredentialCodingKey)
- let displayName = coder.decodeObject(
- of: NSString.self,
- forKey: kDisplayNameCodingKey
- ) as? String
- let photoURL = coder.decodeObject(forKey: kPhotoURLCodingKey) as? URL
- let email = coder.decodeObject(of: NSString.self, forKey: kEmailCodingKey) as? String
- let phoneNumber = coder.decodeObject(
- of: NSString.self,
- forKey: kPhoneNumberCodingKey
- ) as? String
- let emailVerified = coder.decodeBool(forKey: kEmailVerifiedCodingKey)
- let providerData = coder.decodeObject(forKey: kProviderDataKey) as? [String: UserInfoImpl]
- let metadata = coder.decodeObject(forKey: kMetadataCodingKey) as? UserMetadata
- let tenantID = coder.decodeObject(of: NSString.self, forKey: kTenantIDCodingKey) as? String
- #if os(iOS)
- let multiFactor = coder.decodeObject(forKey: kMultiFactorCodingKey) as? MultiFactor
- #endif
- self.tokenService = tokenService
- uid = userID
- isAnonymous = anonymous
- self.hasEmailPasswordCredential = hasEmailPasswordCredential
- self.email = email
- isEmailVerified = emailVerified
- self.displayName = displayName
- self.photoURL = photoURL
- providerDataRaw = providerData ?? [:]
- self.phoneNumber = phoneNumber
- self.metadata = metadata ?? UserMetadata(withCreationDate: nil, lastSignInDate: nil)
- self.tenantID = tenantID
- // The `heartbeatLogger` and `appCheck` will be set later via a property update.
- requestConfiguration = AuthRequestConfiguration(apiKey: apiKey, appID: appID)
- taskQueue = AuthSerialTaskQueue()
- #if os(iOS)
- self.multiFactor = multiFactor ?? MultiFactor()
- super.init()
- multiFactor?.user = self
- #endif
- }
- }
|