| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190 |
- /*
- * Copyright 2017 Google
- *
- * 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/Foundation.h>
- #import "Private/FIRUser_Internal.h"
- #import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h"
- #import "AuthProviders/EmailPassword/FIREmailAuthProvider.h"
- #import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h"
- #import "Private/FIRAdditionalUserInfo_Internal.h"
- #import "FIRAuth.h"
- #import "Private/FIRAuthCredential_Internal.h"
- #import "Private/FIRAuthDataResult_Internal.h"
- #import "Private/FIRAuthErrorUtils.h"
- #import "Private/FIRAuthGlobalWorkQueue.h"
- #import "Private/FIRAuthSerialTaskQueue.h"
- #import "Private/FIRAuth_Internal.h"
- #import "FIRSecureTokenService.h"
- #import "FIRUserInfoImpl.h"
- #import "FIRAuthBackend.h"
- #import "FIRDeleteAccountRequest.h"
- #import "FIRDeleteAccountResponse.h"
- #import "FIRGetAccountInfoRequest.h"
- #import "FIRGetAccountInfoResponse.h"
- #import "FIRGetOOBConfirmationCodeRequest.h"
- #import "FIRGetOOBConfirmationCodeResponse.h"
- #import "FIRSetAccountInfoRequest.h"
- #import "FIRSetAccountInfoResponse.h"
- #import "FIRVerifyAssertionRequest.h"
- #import "FIRVerifyAssertionResponse.h"
- #import "FIRVerifyCustomTokenRequest.h"
- #import "FIRVerifyCustomTokenResponse.h"
- #import "FIRVerifyPasswordRequest.h"
- #import "FIRVerifyPasswordResponse.h"
- #import "FIRVerifyPhoneNumberRequest.h"
- #import "FIRVerifyPhoneNumberResponse.h"
- #if TARGET_OS_IOS
- #import "AuthProviders/Phone/FIRPhoneAuthProvider.h"
- #endif
- NS_ASSUME_NONNULL_BEGIN
- /** @var kUserIDCodingKey
- @brief The key used to encode the user ID for NSSecureCoding.
- */
- static NSString *const kUserIDCodingKey = @"userID";
- /** @var kHasEmailPasswordCredentialCodingKey
- @brief The key used to encode the hasEmailPasswordCredential property for NSSecureCoding.
- */
- static NSString *const kHasEmailPasswordCredentialCodingKey = @"hasEmailPassword";
- /** @var kAnonymousCodingKey
- @brief The key used to encode the anonymous property for NSSecureCoding.
- */
- static NSString *const kAnonymousCodingKey = @"anonymous";
- /** @var kEmailCodingKey
- @brief The key used to encode the email property for NSSecureCoding.
- */
- static NSString *const kEmailCodingKey = @"email";
- /** @var kPhoneNumberCodingKey
- @brief The key used to encode the phoneNumber property for NSSecureCoding.
- */
- static NSString *const kPhoneNumberCodingKey = @"phoneNumber";
- /** @var kEmailVerifiedCodingKey
- @brief The key used to encode the isEmailVerified property for NSSecureCoding.
- */
- static NSString *const kEmailVerifiedCodingKey = @"emailVerified";
- /** @var kDisplayNameCodingKey
- @brief The key used to encode the displayName property for NSSecureCoding.
- */
- static NSString *const kDisplayNameCodingKey = @"displayName";
- /** @var kPhotoURLCodingKey
- @brief The key used to encode the photoURL property for NSSecureCoding.
- */
- static NSString *const kPhotoURLCodingKey = @"photoURL";
- /** @var kProviderDataKey
- @brief The key used to encode the providerData instance variable for NSSecureCoding.
- */
- static NSString *const kProviderDataKey = @"providerData";
- /** @var kAPIKeyCodingKey
- @brief The key used to encode the APIKey instance variable for NSSecureCoding.
- */
- static NSString *const kAPIKeyCodingKey = @"APIKey";
- /** @var kTokenServiceCodingKey
- @brief The key used to encode the tokenService instance variable for NSSecureCoding.
- */
- static NSString *const kTokenServiceCodingKey = @"tokenService";
- /** @var kMissingUsersErrorMessage
- @brief The error message when there is no users array in the getAccountInfo response.
- */
- static NSString *const kMissingUsersErrorMessage = @"users";
- /** @typedef CallbackWithError
- @brief The type for a callback block that only takes an error parameter.
- */
- typedef void (^CallbackWithError)(NSError *_Nullable);
- /** @typedef CallbackWithUserAndError
- @brief The type for a callback block that takes a user parameter and an error parameter.
- */
- typedef void (^CallbackWithUserAndError)(FIRUser *_Nullable, NSError *_Nullable);
- /** @typedef CallbackWithUserAndError
- @brief The type for a callback block that takes a user parameter and an error parameter.
- */
- typedef void (^CallbackWithAuthDataResultAndError)(FIRAuthDataResult *_Nullable,
- NSError *_Nullable);
- /** @var kMissingPasswordReason
- @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
- @remarks This error message will be localized in the future.
- */
- static NSString *const kMissingPasswordReason = @"Missing Password";
- /** @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.
- */
- static void callInMainThreadWithError(_Nullable CallbackWithError callback,
- NSError *_Nullable error) {
- if (callback) {
- dispatch_async(dispatch_get_main_queue(), ^{
- 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.
- */
- static void callInMainThreadWithUserAndError(_Nullable CallbackWithUserAndError callback,
- FIRUser *_Nonnull user,
- NSError *_Nullable error) {
- if (callback) {
- dispatch_async(dispatch_get_main_queue(), ^{
- callback(error ? nil : user, 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 result The result to pass to callback if there is no error.
- @param error The error to pass to callback.
- */
- static void callInMainThreadWithAuthDataResultAndError(
- _Nullable CallbackWithAuthDataResultAndError callback,
- FIRAuthDataResult *_Nullable result,
- NSError *_Nullable error) {
- if (callback) {
- dispatch_async(dispatch_get_main_queue(), ^{
- callback(result, error);
- });
- }
- }
- @interface FIRUserProfileChangeRequest ()
- /** @fn initWithUser:
- @brief Designated initializer.
- @param user The user for which we are updating profile information.
- */
- - (nullable instancetype)initWithUser:(FIRUser *)user NS_DESIGNATED_INITIALIZER;
- @end
- @interface FIRUser ()
- /** @fn initWithAPIKey:
- @brief Designated initializer
- @param APIKey The client API key for making RPCs.
- */
- - (nullable instancetype)initWithAPIKey:(NSString *)APIKey NS_DESIGNATED_INITIALIZER;
- @end
- @implementation FIRUser {
- /** @var _hasEmailPasswordCredential
- @brief Whether or not the user can be authenticated by using Firebase email and password.
- */
- BOOL _hasEmailPasswordCredential;
- /** @var _providerData
- @brief Provider specific user data.
- */
- NSDictionary<NSString *, FIRUserInfoImpl *> *_providerData;
- /** @var _APIKey
- @brief The application's API Key.
- */
- NSString *_APIKey;
- /** @var _taskQueue
- @brief Used to serialize the update profile calls.
- */
- FIRAuthSerialTaskQueue *_taskQueue;
- /** @var _tokenService
- @brief A secure token service associated with this user. For performing token exchanges and
- refreshing access tokens.
- */
- FIRSecureTokenService *_tokenService;
- }
- #pragma mark - Properties
- // Explicitly @synthesize because these properties are defined in FIRUserInfo protocol.
- @synthesize uid = _userID;
- @synthesize displayName = _displayName;
- @synthesize photoURL = _photoURL;
- @synthesize email = _email;
- @synthesize phoneNumber = _phoneNumber;
- #pragma mark -
- + (void)retrieveUserWithAPIKey:(NSString *)APIKey
- accessToken:(NSString *)accessToken
- accessTokenExpirationDate:(NSDate *)accessTokenExpirationDate
- refreshToken:(NSString *)refreshToken
- anonymous:(BOOL)anonymous
- callback:(FIRRetrieveUserCallback)callback {
- FIRSecureTokenService *tokenService =
- [[FIRSecureTokenService alloc] initWithAPIKey:APIKey
- accessToken:accessToken
- accessTokenExpirationDate:accessTokenExpirationDate
- refreshToken:refreshToken];
- FIRUser *user = [[self alloc] initWithAPIKey:APIKey
- tokenService:tokenService];
- [user internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
- if (error) {
- callback(nil, error);
- return;
- }
- FIRGetAccountInfoRequest *getAccountInfoRequest =
- [[FIRGetAccountInfoRequest alloc] initWithAPIKey:APIKey accessToken:accessToken];
- [FIRAuthBackend getAccountInfo:getAccountInfoRequest
- callback:^(FIRGetAccountInfoResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- callback(nil, error);
- return;
- }
- user->_anonymous = anonymous;
- [user updateWithGetAccountInfoResponse:response];
- callback(user, nil);
- }];
- }];
- }
- - (nullable instancetype)initWithAPIKey:(NSString *)APIKey {
- self = [super init];
- if (self) {
- _APIKey = [APIKey copy];
- _providerData = @{ };
- _taskQueue = [[FIRAuthSerialTaskQueue alloc] init];
- }
- return self;
- }
- - (nullable instancetype)initWithAPIKey:(NSString *)APIKey
- tokenService:(FIRSecureTokenService *)tokenService {
- self = [self initWithAPIKey:APIKey];
- if (self) {
- _tokenService = tokenService;
- }
- return self;
- }
- #pragma mark - NSSecureCoding
- + (BOOL)supportsSecureCoding {
- return YES;
- }
- - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
- NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDCodingKey];
- BOOL hasAnonymousKey = [aDecoder containsValueForKey:kAnonymousCodingKey];
- BOOL anonymous = [aDecoder decodeBoolForKey:kAnonymousCodingKey];
- BOOL hasEmailPasswordCredential =
- [aDecoder decodeBoolForKey:kHasEmailPasswordCredentialCodingKey];
- NSString *displayName =
- [aDecoder decodeObjectOfClass:[NSString class] forKey:kDisplayNameCodingKey];
- NSURL *photoURL =
- [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPhotoURLCodingKey];
- NSString *email =
- [aDecoder decodeObjectOfClass:[NSString class] forKey:kEmailCodingKey];
- NSString *phoneNumber =
- [aDecoder decodeObjectOfClass:[NSString class] forKey:kPhoneNumberCodingKey];
- BOOL emailVerified = [aDecoder decodeBoolForKey:kEmailVerifiedCodingKey];
- NSSet *providerDataClasses = [NSSet setWithArray:@[
- [NSDictionary class],
- [NSString class],
- [FIRUserInfoImpl class]
- ]];
- NSDictionary<NSString *, FIRUserInfoImpl *> *providerData =
- [aDecoder decodeObjectOfClasses:providerDataClasses forKey:kProviderDataKey];
- NSString *APIKey =
- [aDecoder decodeObjectOfClass:[NSString class] forKey:kAPIKeyCodingKey];
- FIRSecureTokenService *tokenService =
- [aDecoder decodeObjectOfClass:[FIRSecureTokenService class] forKey:kTokenServiceCodingKey];
- if (!userID || !APIKey || !tokenService) {
- return nil;
- }
- self = [self initWithAPIKey:APIKey];
- if (self) {
- _tokenService = tokenService;
- _userID = userID;
- // Previous version of this code didn't save 'anonymous' bit directly but deduced it from
- // 'hasEmailPasswordCredential' and 'providerData' instead, so here backward compatibility is
- // provided to read old format data.
- _anonymous = hasAnonymousKey ? anonymous : (!hasEmailPasswordCredential && !providerData.count);
- _hasEmailPasswordCredential = hasEmailPasswordCredential;
- _email = email;
- _emailVerified = emailVerified;
- _displayName = displayName;
- _photoURL = photoURL;
- _providerData = providerData;
- _phoneNumber = phoneNumber;
- }
- return self;
- }
- - (void)encodeWithCoder:(NSCoder *)aCoder {
- [aCoder encodeObject:_userID forKey:kUserIDCodingKey];
- [aCoder encodeBool:_anonymous forKey:kAnonymousCodingKey];
- [aCoder encodeBool:_hasEmailPasswordCredential forKey:kHasEmailPasswordCredentialCodingKey];
- [aCoder encodeObject:_providerData forKey:kProviderDataKey];
- [aCoder encodeObject:_email forKey:kEmailCodingKey];
- [aCoder encodeObject:_phoneNumber forKey:kPhoneNumberCodingKey];
- [aCoder encodeBool:_emailVerified forKey:kEmailVerifiedCodingKey];
- [aCoder encodeObject:_photoURL forKey:kPhotoURLCodingKey];
- [aCoder encodeObject:_displayName forKey:kDisplayNameCodingKey];
- [aCoder encodeObject:_APIKey forKey:kAPIKeyCodingKey];
- [aCoder encodeObject:_tokenService forKey:kTokenServiceCodingKey];
- }
- #pragma mark -
- - (NSString *)providerID {
- return @"Firebase";
- }
- - (NSArray<id<FIRUserInfo>> *)providerData {
- return _providerData.allValues;
- }
- /** @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.
- */
- - (void)getAccountInfoRefreshingCache:(void(^)(FIRGetAccountInfoResponseUser *_Nullable user,
- NSError *_Nullable error))callback {
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
- if (error) {
- callback(nil, error);
- return;
- }
- FIRGetAccountInfoRequest *getAccountInfoRequest =
- [[FIRGetAccountInfoRequest alloc] initWithAPIKey:_APIKey accessToken:accessToken];
- [FIRAuthBackend getAccountInfo:getAccountInfoRequest
- callback:^(FIRGetAccountInfoResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- callback(nil, error);
- return;
- }
- [self updateWithGetAccountInfoResponse:response];
- if (![self updateKeychain:&error]) {
- callback(nil, error);
- return;
- }
- callback(response.users.firstObject, nil);
- }];
- }];
- }
- - (void)updateWithGetAccountInfoResponse:(FIRGetAccountInfoResponse *)response {
- FIRGetAccountInfoResponseUser *user = response.users.firstObject;
- _userID = user.localID;
- _email = user.email;
- _emailVerified = user.emailVerified;
- _displayName = user.displayName;
- _photoURL = user.photoURL;
- _phoneNumber = user.phoneNumber;
- _hasEmailPasswordCredential = user.passwordHash.length > 0;
- NSMutableDictionary<NSString *, FIRUserInfoImpl *> *providerData =
- [NSMutableDictionary dictionary];
- for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in user.providerUserInfo) {
- FIRUserInfoImpl *userInfo =
- [FIRUserInfoImpl userInfoWithGetAccountInfoResponseProviderUserInfo:providerUserInfo];
- if (userInfo) {
- providerData[providerUserInfo.providerID] = userInfo;
- }
- }
- _providerData = [providerData copy];
- }
- /** @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.
- */
- - (void)executeUserUpdateWithChanges:(void(^)(FIRGetAccountInfoResponseUser *,
- FIRSetAccountInfoRequest *))changeBlock
- callback:(nonnull FIRUserProfileChangeCallback)callback {
- [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
- [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
- NSError *_Nullable error) {
- if (error) {
- complete();
- callback(error);
- return;
- }
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- complete();
- callback(error);
- return;
- }
- // Mutate setAccountInfoRequest in block:
- FIRSetAccountInfoRequest *setAccountInfoRequest =
- [[FIRSetAccountInfoRequest alloc] initWithAPIKey:_APIKey];
- setAccountInfoRequest.accessToken = accessToken;
- changeBlock(user, setAccountInfoRequest);
- // Execute request:
- [FIRAuthBackend setAccountInfo:setAccountInfoRequest
- callback:^(FIRSetAccountInfoResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- complete();
- callback(error);
- return;
- }
- if (response.IDToken && response.refreshToken) {
- FIRSecureTokenService *tokenService =
- [[FIRSecureTokenService alloc] initWithAPIKey:_APIKey
- accessToken:response.IDToken
- accessTokenExpirationDate:response.approximateExpirationDate
- refreshToken:response.refreshToken];
- [self setTokenService:tokenService callback:^(NSError *_Nullable error) {
- complete();
- callback(error);
- }];
- return;
- }
- complete();
- callback(nil);
- }];
- }];
- }];
- }];
- }
- /** @fn updateKeychain:
- @brief Updates the keychain for user token or info changes.
- @param error The error if NO is returned.
- @return Wether the operation is successful.
- */
- - (BOOL)updateKeychain:(NSError *_Nullable *_Nullable)error {
- return !_auth || [_auth updateKeychainWithUser:self error:error];
- }
- /** @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.
- */
- - (void)setTokenService:(FIRSecureTokenService *)tokenService
- callback:(nonnull CallbackWithError)callback {
- [tokenService fetchAccessTokenForcingRefresh:NO
- callback:^(NSString *_Nullable token,
- NSError *_Nullable error,
- BOOL tokenUpdated) {
- if (error) {
- callback(error);
- return;
- }
- _tokenService = tokenService;
- if (![self updateKeychain:&error]) {
- callback(error);
- return;
- }
- [_auth notifyListenersOfAuthStateChangeWithUser:self token:token];
- callback(nil);
- }];
- }
- #pragma mark -
- /** @fn updateEmail:password:callback:
- @brief Updates email address and/or password for the current user.
- @remarks May fail if there is already an email/password-based account for the same email
- address.
- @param email The email address for the user, if to be updated.
- @param password The new password for the user, if to be updated.
- @param callback The block called when the user profile change has finished. Invoked
- asynchronously on the auth global work queue in the future.
- @remarks May fail with a @c FIRAuthErrorCodeRequiresRecentLogin error code.
- Call @c reauthentateWithCredential:completion: beforehand to avoid this error case.
- */
- - (void)updateEmail:(nullable NSString *)email
- password:(nullable NSString *)password
- callback:(nonnull FIRUserProfileChangeCallback)callback {
- if (password && ![password length]){
- callback([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]);
- return;
- }
- BOOL hadEmailPasswordCredential = _hasEmailPasswordCredential;
- [self executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
- FIRSetAccountInfoRequest *request) {
- if (email) {
- request.email = email;
- }
- if (password) {
- request.password = password;
- }
- }
- callback:^(NSError *error) {
- if (error) {
- callback(error);
- return;
- }
- if (email) {
- _email = email;
- }
- if (_email && password) {
- _anonymous = NO;
- _hasEmailPasswordCredential = YES;
- if (!hadEmailPasswordCredential) {
- // The list of providers need to be updated for the newly added email-password provider.
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- callback(error);
- return;
- }
- FIRGetAccountInfoRequest *getAccountInfoRequest =
- [[FIRGetAccountInfoRequest alloc] initWithAPIKey:_APIKey accessToken:accessToken];
- [FIRAuthBackend getAccountInfo:getAccountInfoRequest
- callback:^(FIRGetAccountInfoResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- callback(error);
- return;
- }
- [self updateWithGetAccountInfoResponse:response];
- if (![self updateKeychain:&error]) {
- callback(error);
- return;
- }
- callback(nil);
- }];
- }];
- return;
- }
- }
- if (![self updateKeychain:&error]) {
- callback(error);
- return;
- }
- callback(nil);
- }];
- }
- - (void)updateEmail:(NSString *)email completion:(nullable FIRUserProfileChangeCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self updateEmail:email password:nil callback:^(NSError *_Nullable error) {
- callInMainThreadWithError(completion, error);
- }];
- });
- }
- - (void)updatePassword:(NSString *)password
- completion:(nullable FIRUserProfileChangeCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self updateEmail:nil password:password callback:^(NSError *_Nullable error){
- callInMainThreadWithError(completion, error);
- }];
- });
- }
- #if TARGET_OS_IOS
- /** @fn internalUpdatePhoneNumberCredential:completion:
- @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 firebaes 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 global work queue in the future.
- */
- - (void)internalUpdatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
- completion:(FIRUserProfileChangeCallback)completion {
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- completion(error);
- return;
- }
- FIRVerifyPhoneNumberRequest *request = [[FIRVerifyPhoneNumberRequest alloc]
- initWithVerificationID:phoneAuthCredential.verificationID
- verificationCode:phoneAuthCredential.verificationCode
- APIKey:_APIKey];
- request.accessToken = accessToken;
- [FIRAuthBackend verifyPhoneNumber:request
- callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- completion(error);;
- return;
- }
- // Get account info to update cached user info.
- [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
- NSError *_Nullable error) {
- if (![self updateKeychain:&error]) {
- completion(error);
- return;
- }
- completion(nil);
- }];
- }];
- }];
- }
- - (void)updatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
- completion:(nullable FIRUserProfileChangeCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self internalUpdatePhoneNumberCredential:phoneAuthCredential
- completion:^(NSError *_Nullable error) {
- callInMainThreadWithError(completion, error);
- }];
- });
- }
- #endif
- - (FIRUserProfileChangeRequest *)profileChangeRequest {
- __block FIRUserProfileChangeRequest *result;
- dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = [[FIRUserProfileChangeRequest alloc] initWithUser:self];
- });
- return result;
- }
- - (void)setDisplayName:(NSString *)displayName {
- _displayName = [displayName copy];
- }
- - (void)setPhotoURL:(NSURL *)photoURL {
- _photoURL = [photoURL copy];
- }
- - (NSString *)rawAccessToken {
- return _tokenService.rawAccessToken;
- }
- - (NSDate *)accessTokenExpirationDate {
- return _tokenService.accessTokenExpirationDate;
- }
- #pragma mark -
- - (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
- NSError *_Nullable error) {
- callInMainThreadWithError(completion, error);
- }];
- });
- }
- #pragma mark -
- - (void)reauthenticateWithCredential:(FIRAuthCredential *)credential
- completion:(nullable FIRUserProfileChangeCallback)completion {
- FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable authResult,
- NSError *_Nullable error) {
- completion(error);
- };
- [self reauthenticateAndRetrieveDataWithCredential:credential completion:callback];
- }
- - (void)
- reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential
- completion:(nullable FIRAuthDataResultCallback) completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [_auth internalSignInAndRetrieveDataWithCredential:credential
- isReauthentication:YES
- callback:^(FIRAuthDataResult *_Nullable authResult,
- NSError *_Nullable error) {
- if (error) {
- // If "user not found" error returned by backend, translate to user mismatch error which is
- // more accurate.
- if (error.code == FIRAuthErrorCodeUserNotFound) {
- error = [FIRAuthErrorUtils userMismatchError];
- }
- callInMainThreadWithAuthDataResultAndError(completion, authResult, error);
- return;
- }
- if (![authResult.user.uid isEqual:_auth.currentUser.uid]) {
- callInMainThreadWithAuthDataResultAndError(completion, authResult,
- [FIRAuthErrorUtils userMismatchError]);
- return;
- }
- // Successful reauthenticate
- [self setTokenService:authResult.user->_tokenService callback:^(NSError *_Nullable error) {
- callInMainThreadWithAuthDataResultAndError(completion, authResult, error);
- }];
- }];
- });
- }
- - (nullable NSString *)refreshToken {
- __block NSString *result;
- dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = _tokenService.refreshToken;
- });
- return result;
- }
- - (void)getIDTokenWithCompletion:(nullable FIRAuthTokenCallback)completion {
- // |getTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to
- // global work queue here.
- [self getIDTokenForcingRefresh:NO completion:completion];
- }
- - (void)getTokenWithCompletion:(nullable FIRAuthTokenCallback)completion {
- [self getIDTokenWithCompletion:completion];
- }
- - (void)getIDTokenForcingRefresh:(BOOL)forceRefresh
- completion:(nullable FIRAuthTokenCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self internalGetTokenForcingRefresh:forceRefresh
- callback:^(NSString *_Nullable token, NSError *_Nullable error) {
- if (completion) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completion(token, error);
- });
- }
- }];
- });
- }
- - (void)getTokenForcingRefresh:(BOOL)forceRefresh
- completion:(nullable FIRAuthTokenCallback)completion {
- [self getIDTokenForcingRefresh:forceRefresh completion:completion];
- }
- /** @fn internalGetTokenForcingRefresh:callback:
- @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.
- */
- - (void)internalGetTokenWithCallback:(nonnull FIRAuthTokenCallback)callback {
- [self internalGetTokenForcingRefresh:NO callback:callback];
- }
- - (void)internalGetTokenForcingRefresh:(BOOL)forceRefresh
- callback:(nonnull FIRAuthTokenCallback)callback {
- [_tokenService fetchAccessTokenForcingRefresh:forceRefresh
- callback:^(NSString *_Nullable token,
- NSError *_Nullable error,
- BOOL tokenUpdated) {
- if (error) {
- callback(nil, error);
- return;
- }
- if (tokenUpdated) {
- if (![self updateKeychain:&error]) {
- callback(nil, error);
- return;
- }
- [_auth notifyListenersOfAuthStateChangeWithUser:self token:token];
- }
- callback(token, nil);
- }];
- }
- - (void)linkWithCredential:(FIRAuthCredential *)credential
- completion:(nullable FIRAuthResultCallback)completion {
- FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable authResult,
- NSError *_Nullable error) {
- completion(authResult.user, error);
- };
- [self linkAndRetrieveDataWithCredential:credential completion:callback];
- }
- - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
- completion:(nullable FIRAuthDataResultCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- if (_providerData[credential.provider]) {
- callInMainThreadWithAuthDataResultAndError(completion,
- nil,
- [FIRAuthErrorUtils providerAlreadyLinkedError]);
- return;
- }
- FIRAuthDataResult *result =
- [[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:nil];
- if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
- if (_hasEmailPasswordCredential) {
- callInMainThreadWithAuthDataResultAndError(completion,
- nil,
- [FIRAuthErrorUtils providerAlreadyLinkedError]);
- return;
- }
- FIREmailPasswordAuthCredential *emailPasswordCredential =
- (FIREmailPasswordAuthCredential *)credential;
- [self updateEmail:emailPasswordCredential.email
- password:emailPasswordCredential.password
- callback:^(NSError *error) {
- if (error) {
- callInMainThreadWithAuthDataResultAndError(completion, nil, error);
- } else {
- callInMainThreadWithAuthDataResultAndError(completion, result, nil);
- }
- }];
- return;
- }
- #if TARGET_OS_IOS
- if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) {
- FIRPhoneAuthCredential *phoneAuthCredential = (FIRPhoneAuthCredential *)credential;
- [self internalUpdatePhoneNumberCredential:phoneAuthCredential
- completion:^(NSError *_Nullable error) {
- if (error){
- callInMainThreadWithAuthDataResultAndError(completion, nil, error);
- } else {
- callInMainThreadWithAuthDataResultAndError(completion, result, nil);
- }
- }];
- return;
- }
- #endif
- [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
- CallbackWithAuthDataResultAndError completeWithError =
- ^(FIRAuthDataResult *result, NSError *error) {
- complete();
- callInMainThreadWithAuthDataResultAndError(completion, result, error);
- };
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- completeWithError(nil, error);
- return;
- }
- FIRVerifyAssertionRequest *request =
- [[FIRVerifyAssertionRequest alloc] initWithAPIKey:_APIKey providerID:credential.provider];
- [credential prepareVerifyAssertionRequest:request];
- request.accessToken = accessToken;
- [FIRAuthBackend verifyAssertion:request
- callback:^(FIRVerifyAssertionResponse *response, NSError *error) {
- if (error) {
- completeWithError(nil, error);
- return;
- }
- FIRAdditionalUserInfo *additionalUserInfo =
- [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
- FIRAuthDataResult *result =
- [[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:additionalUserInfo];
- // Update the new token and refresh user info again.
- _tokenService =
- [[FIRSecureTokenService alloc] initWithAPIKey:_APIKey
- accessToken:response.IDToken
- accessTokenExpirationDate:response.approximateExpirationDate
- refreshToken:response.refreshToken];
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- completeWithError(nil, error);
- return;
- }
- FIRGetAccountInfoRequest *getAccountInfoRequest =
- [[FIRGetAccountInfoRequest alloc] initWithAPIKey:_APIKey accessToken:accessToken];
- [FIRAuthBackend getAccountInfo:getAccountInfoRequest
- callback:^(FIRGetAccountInfoResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- completeWithError(nil, error);
- return;
- }
- _anonymous = NO;
- [self updateWithGetAccountInfoResponse:response];
- if (![self updateKeychain:&error]) {
- completeWithError(nil, error);
- return;
- }
- completeWithError(result, nil);
- }];
- }];
- }];
- }];
- }];
- });
- }
- - (void)unlinkFromProvider:(NSString *)provider
- completion:(nullable FIRAuthResultCallback)completion {
- [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
- CallbackWithError completeAndCallbackWithError = ^(NSError *error) {
- complete();
- callInMainThreadWithUserAndError(completion, self, error);
- };
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- completeAndCallbackWithError(error);
- return;
- }
- FIRSetAccountInfoRequest *setAccountInfoRequest =
- [[FIRSetAccountInfoRequest alloc] initWithAPIKey:_APIKey];
- setAccountInfoRequest.accessToken = accessToken;
- BOOL isEmailPasswordProvider = [provider isEqualToString:FIREmailAuthProviderID];
- if (isEmailPasswordProvider) {
- if (!_hasEmailPasswordCredential) {
- completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
- return;
- }
- setAccountInfoRequest.deleteAttributes = @[ FIRSetAccountInfoUserAttributePassword ];
- } else {
- if (!_providerData[provider]) {
- completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
- return;
- }
- setAccountInfoRequest.deleteProviders = @[ provider ];
- }
- [FIRAuthBackend setAccountInfo:setAccountInfoRequest
- callback:^(FIRSetAccountInfoResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- completeAndCallbackWithError(error);
- return;
- }
- if (isEmailPasswordProvider) {
- _hasEmailPasswordCredential = NO;
- } else {
- // We can't just use the provider info objects in FIRSetAcccountInfoResponse because they
- // don't have localID and email fields. Remove the specific provider manually.
- NSMutableDictionary *mutableProviderData = [_providerData mutableCopy];
- [mutableProviderData removeObjectForKey:provider];
- _providerData = [mutableProviderData copy];
- #if TARGET_OS_IOS
- // After successfully unlinking a phone auth provider, remove the phone number from the
- // cached user info.
- if ([provider isEqualToString:FIRPhoneAuthProviderID]) {
- _phoneNumber = nil;
- }
- #endif
- }
- if (response.IDToken && response.refreshToken) {
- FIRSecureTokenService *tokenService =
- [[FIRSecureTokenService alloc] initWithAPIKey:_APIKey
- accessToken:response.IDToken
- accessTokenExpirationDate:response.approximateExpirationDate
- refreshToken:response.refreshToken];
- [self setTokenService:tokenService callback:^(NSError *_Nullable error) {
- completeAndCallbackWithError(error);
- }];
- return;
- }
- if (![self updateKeychain:&error]) {
- completeAndCallbackWithError(error);
- return;
- }
- completeAndCallbackWithError(nil);
- }];
- }];
- }];
- }
- - (void)sendEmailVerificationWithCompletion:(nullable FIRSendEmailVerificationCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- callInMainThreadWithError(completion, error);
- return;
- }
- FIRGetOOBConfirmationCodeRequest *request =
- [FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:accessToken
- APIKey:_APIKey];
- [FIRAuthBackend getOOBConfirmationCode:request
- callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable
- response,
- NSError *_Nullable error) {
- callInMainThreadWithError(completion, error);
- }];
- }];
- });
- }
- - (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
- NSError *_Nullable error) {
- if (error) {
- callInMainThreadWithError(completion, error);
- return;
- }
- FIRDeleteAccountRequest *deleteUserRequest =
- [[FIRDeleteAccountRequest alloc] initWithAPIKey:_APIKey
- localID:_userID
- accessToken:accessToken];
- [FIRAuthBackend deleteAccount:deleteUserRequest callback:^(NSError *_Nullable error) {
- if (error) {
- callInMainThreadWithError(completion, error);
- return;
- }
- if (![[FIRAuth auth] signOutByForceWithUserID:_userID error:&error]) {
- callInMainThreadWithError(completion, error);
- return;
- }
- callInMainThreadWithError(completion, error);
- }];
- }];
- });
- }
- @end
- @implementation FIRUserProfileChangeRequest {
- /** @var _user
- @brief The user associated with the change request.
- */
- FIRUser *_user;
- /** @var _displayName
- @brief The display name value to set if @c _displayNameSet is YES.
- */
- NSString *_displayName;
- /** @var _displayNameSet
- @brief Indicates the display name should be part of the change request.
- */
- BOOL _displayNameSet;
- /** @var _photoURL
- @brief The photo URL value to set if @c _displayNameSet is YES.
- */
- NSURL *_photoURL;
- /** @var _photoURLSet
- @brief Indicates the photo URL should be part of the change request.
- */
- BOOL _photoURLSet;
- /** @var _consumed
- @brief Indicates the @c commitChangesWithCallback: method has already been invoked.
- */
- BOOL _consumed;
- }
- - (nullable instancetype)initWithUser:(FIRUser *)user {
- self = [super init];
- if (self) {
- _user = user;
- }
- return self;
- }
- - (nullable NSString *)displayName {
- return _displayName;
- }
- - (void)setDisplayName:(nullable NSString *)displayName {
- dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (_consumed) {
- [NSException raise:NSInternalInconsistencyException
- format:@"%@",
- @"Invalid call to setDisplayName: after commitChangesWithCallback:."];
- return;
- }
- _displayNameSet = YES;
- _displayName = [displayName copy];
- });
- }
- - (nullable NSURL *)photoURL {
- return _photoURL;
- }
- - (void)setPhotoURL:(nullable NSURL *)photoURL {
- dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (_consumed) {
- [NSException raise:NSInternalInconsistencyException
- format:@"%@",
- @"Invalid call to setPhotoURL: after commitChangesWithCallback:."];
- return;
- }
- _photoURLSet = YES;
- _photoURL = [photoURL copy];
- });
- }
- /** @fn hasUpdates
- @brief Indicates at least one field has a value which needs to be committed.
- */
- - (BOOL)hasUpdates {
- return _displayNameSet || _photoURLSet;
- }
- - (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
- dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (_consumed) {
- [NSException raise:NSInternalInconsistencyException
- format:@"%@",
- @"commitChangesWithCallback: should only be called once."];
- return;
- }
- _consumed = YES;
- // Return fast if there is nothing to update:
- if (![self hasUpdates]) {
- callInMainThreadWithError(completion, nil);
- return;
- }
- NSString *displayName = [_displayName copy];
- BOOL displayNameWasSet = _displayNameSet;
- NSURL *photoURL = [_photoURL copy];
- BOOL photoURLWasSet = _photoURLSet;
- [_user executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
- FIRSetAccountInfoRequest *request) {
- if (photoURLWasSet) {
- request.photoURL = photoURL;
- }
- if (displayNameWasSet) {
- request.displayName = displayName;
- }
- }
- callback:^(NSError *_Nullable error) {
- if (error) {
- callInMainThreadWithError(completion, error);
- return;
- }
- if (displayNameWasSet) {
- [_user setDisplayName:displayName];
- }
- if (photoURLWasSet) {
- [_user setPhotoURL:photoURL];
- }
- if (![_user updateKeychain:&error]) {
- callInMainThreadWithError(completion, error);
- return;
- }
- callInMainThreadWithError(completion, nil);
- }];
- });
- }
- @end
- NS_ASSUME_NONNULL_END
|