FIRAuth.m 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <Foundation/Foundation.h>
  17. #import "FIRAuth_Internal.h"
  18. #import <FirebaseCore/FIRAppAssociationRegistration.h>
  19. #import <FirebaseCore/FIRAppInternal.h>
  20. #import <FirebaseCore/FIRLogger.h>
  21. #import <FirebaseCore/FIROptions.h>
  22. #import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h"
  23. #import "FIRAdditionalUserInfo_Internal.h"
  24. #import "FIRAuthCredential_Internal.h"
  25. #import "FIRAuthDataResult_Internal.h"
  26. #import "FIRAuthDispatcher.h"
  27. #import "FIRAuthErrorUtils.h"
  28. #import "FIRAuthExceptionUtils.h"
  29. #import "FIRAuthGlobalWorkQueue.h"
  30. #import "FIRAuthKeychain.h"
  31. #import "FIRAuthOperationType.h"
  32. #import "FIRUser_Internal.h"
  33. #import "FirebaseAuth.h"
  34. #import "FIRAuthBackend.h"
  35. #import "FIRAuthRequestConfiguration.h"
  36. #import "FIRCreateAuthURIRequest.h"
  37. #import "FIRCreateAuthURIResponse.h"
  38. #import "FIRGetOOBConfirmationCodeRequest.h"
  39. #import "FIRGetOOBConfirmationCodeResponse.h"
  40. #import "FIRResetPasswordRequest.h"
  41. #import "FIRResetPasswordResponse.h"
  42. #import "FIRSendVerificationCodeRequest.h"
  43. #import "FIRSendVerificationCodeResponse.h"
  44. #import "FIRSetAccountInfoRequest.h"
  45. #import "FIRSetAccountInfoResponse.h"
  46. #import "FIRSignUpNewUserRequest.h"
  47. #import "FIRSignUpNewUserResponse.h"
  48. #import "FIRVerifyAssertionRequest.h"
  49. #import "FIRVerifyAssertionResponse.h"
  50. #import "FIRVerifyCustomTokenRequest.h"
  51. #import "FIRVerifyCustomTokenResponse.h"
  52. #import "FIRVerifyPasswordRequest.h"
  53. #import "FIRVerifyPasswordResponse.h"
  54. #import "FIRVerifyPhoneNumberRequest.h"
  55. #import "FIRVerifyPhoneNumberResponse.h"
  56. #if TARGET_OS_IOS
  57. #import <UIKit/UIKit.h>
  58. #import "FIRAuthAPNSToken.h"
  59. #import "FIRAuthAPNSTokenManager.h"
  60. #import "FIRAuthAppCredentialManager.h"
  61. #import "FIRAuthAppDelegateProxy.h"
  62. #import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h"
  63. #import "FIRAuthNotificationManager.h"
  64. #import "FIRAuthURLPresenter.h"
  65. #endif
  66. #pragma mark - Constants
  67. #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  68. const NSNotificationName FIRAuthStateDidChangeNotification = @"FIRAuthStateDidChangeNotification";
  69. #else
  70. NSString *const FIRAuthStateDidChangeNotification = @"FIRAuthStateDidChangeNotification";
  71. #endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  72. /** @var kMaxWaitTimeForBackoff
  73. @brief The maximum wait time before attempting to retry auto refreshing tokens after a failed
  74. attempt.
  75. @remarks This is the upper limit (in seconds) of the exponential backoff used for retrying
  76. token refresh.
  77. */
  78. static NSTimeInterval kMaxWaitTimeForBackoff = 16 * 60;
  79. /** @var kTokenRefreshHeadStart
  80. @brief The amount of time before the token expires that proactive refresh should be attempted.
  81. */
  82. NSTimeInterval kTokenRefreshHeadStart = 5 * 60;
  83. /** @var kUserKey
  84. @brief Key of user stored in the keychain. Prefixed with a Firebase app name.
  85. */
  86. static NSString *const kUserKey = @"%@_firebase_user";
  87. /** @var kMissingEmailInvalidParameterExceptionReason
  88. @brief The reason for @c invalidParameterException when the email used to initiate password
  89. reset is nil.
  90. */
  91. static NSString *const kMissingEmailInvalidParameterExceptionReason =
  92. @"The email used to initiate password reset cannot be nil.";
  93. /** @var kPasswordResetRequestType
  94. @brief The action code type value for resetting password in the check action code response.
  95. */
  96. static NSString *const kPasswordResetRequestType = @"PASSWORD_RESET";
  97. /** @var kVerifyEmailRequestType
  98. @brief The action code type value for verifying email in the check action code response.
  99. */
  100. static NSString *const kVerifyEmailRequestType = @"VERIFY_EMAIL";
  101. /** @var kRecoverEmailRequestType
  102. @brief The action code type value for recovering email in the check action code response.
  103. */
  104. static NSString *const kRecoverEmailRequestType = @"RECOVER_EMAIL";
  105. /** @var kMissingPasswordReason
  106. @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
  107. @remarks This error message will be localized in the future.
  108. */
  109. static NSString *const kMissingPasswordReason = @"Missing Password";
  110. /** @var gKeychainServiceNameForAppName
  111. @brief A map from Firebase app name to keychain service names.
  112. @remarks This map is needed for looking up the keychain service name after the FIRApp instance
  113. is deleted, to remove the associated keychain item. Accessing should occur within a
  114. @syncronized([FIRAuth class]) context.
  115. */
  116. static NSMutableDictionary *gKeychainServiceNameForAppName;
  117. #pragma mark - FIRActionCodeInfo
  118. @implementation FIRActionCodeInfo {
  119. /** @var _email
  120. @brief The email address to which the code was sent. The new email address in the case of
  121. FIRActionCodeOperationRecoverEmail.
  122. */
  123. NSString *_email;
  124. /** @var _fromEmail
  125. @brief The current email address in the case of FIRActionCodeOperationRecoverEmail.
  126. */
  127. NSString *_fromEmail;
  128. }
  129. - (NSString *)dataForKey:(FIRActionDataKey)key{
  130. switch (key) {
  131. case FIRActionCodeEmailKey:
  132. return _email;
  133. case FIRActionCodeFromEmailKey:
  134. return _fromEmail;
  135. }
  136. }
  137. - (instancetype)initWithOperation:(FIRActionCodeOperation)operation
  138. email:(NSString *)email
  139. newEmail:(nullable NSString *)newEmail {
  140. self = [super init];
  141. if (self) {
  142. _operation = operation;
  143. if (newEmail) {
  144. _email = [newEmail copy];
  145. _fromEmail = [email copy];
  146. } else {
  147. _email = [email copy];
  148. }
  149. }
  150. return self;
  151. }
  152. /** @fn actionCodeOperationForRequestType:
  153. @brief Returns the corresponding operation type per provided request type string.
  154. @param requestType Request type returned in in the server response.
  155. @return The corresponding FIRActionCodeOperation for the supplied request type.
  156. */
  157. + (FIRActionCodeOperation)actionCodeOperationForRequestType:(NSString *)requestType {
  158. if ([requestType isEqualToString:kPasswordResetRequestType]) {
  159. return FIRActionCodeOperationPasswordReset;
  160. }
  161. if ([requestType isEqualToString:kVerifyEmailRequestType]) {
  162. return FIRActionCodeOperationVerifyEmail;
  163. }
  164. if ([requestType isEqualToString:kRecoverEmailRequestType]) {
  165. return FIRActionCodeOperationRecoverEmail;
  166. }
  167. return FIRActionCodeOperationUnknown;
  168. }
  169. @end
  170. #pragma mark - FIRAuth
  171. #if TARGET_OS_IOS
  172. @interface FIRAuth () <FIRAuthAppDelegateHandler>
  173. #else
  174. @interface FIRAuth ()
  175. #endif
  176. /** @property firebaseAppId
  177. @brief The Firebase app ID.
  178. */
  179. @property(nonatomic, copy, readonly) NSString *firebaseAppId;
  180. /** @property additionalFrameworkMarker
  181. @brief Additional framework marker that will be added as part of the header of every request.
  182. */
  183. @property(nonatomic, copy, nullable) NSString *additionalFrameworkMarker;
  184. /** @fn initWithApp:
  185. @brief Creates a @c FIRAuth instance associated with the provided @c FIRApp instance.
  186. @param app The application to associate the auth instance with.
  187. */
  188. - (instancetype)initWithApp:(FIRApp *)app;
  189. @end
  190. @implementation FIRAuth {
  191. /** @var _currentUser
  192. @brief The current user.
  193. */
  194. FIRUser *_currentUser;
  195. /** @var _firebaseAppName
  196. @brief The Firebase app name.
  197. */
  198. NSString *_firebaseAppName;
  199. /** @var _listenerHandles
  200. @brief Handles returned from @c NSNotificationCenter for blocks which are "auth state did
  201. change" notification listeners.
  202. @remarks Mutations should occur within a @syncronized(self) context.
  203. */
  204. NSMutableArray<FIRAuthStateDidChangeListenerHandle> *_listenerHandles;
  205. /** @var _keychain
  206. @brief The keychain service.
  207. */
  208. FIRAuthKeychain *_keychain;
  209. /** @var _lastNotifiedUserToken
  210. @brief The user access (ID) token used last time for posting auth state changed notification.
  211. */
  212. NSString *_lastNotifiedUserToken;
  213. /** @var _autoRefreshTokens
  214. @brief This flag denotes whether or not tokens should be automatically refreshed.
  215. @remarks Will only be set to @YES if the another Firebase service is included (additionally to
  216. Firebase Auth).
  217. */
  218. BOOL _autoRefreshTokens;
  219. /** @var _autoRefreshScheduled
  220. @brief Whether or not token auto-refresh is currently scheduled.
  221. */
  222. BOOL _autoRefreshScheduled;
  223. /** @var _isAppInBackground
  224. @brief A flag that is set to YES if the app is put in the background and no when the app is
  225. returned to the foreground.
  226. */
  227. BOOL _isAppInBackground;
  228. /** @var _applicationDidBecomeActiveObserver
  229. @brief An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification.
  230. */
  231. id<NSObject> _applicationDidBecomeActiveObserver;
  232. /** @var _applicationDidBecomeActiveObserver
  233. @brief An opaque object to act as the observer for
  234. UIApplicationDidEnterBackgroundNotification.
  235. */
  236. id<NSObject> _applicationDidEnterBackgroundObserver;
  237. }
  238. + (void)load {
  239. static dispatch_once_t onceToken;
  240. dispatch_once(&onceToken, ^{
  241. gKeychainServiceNameForAppName = [[NSMutableDictionary alloc] init];
  242. NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  243. // Ensures the @c FIRAuth instance for a given app gets loaded as soon as the app is ready.
  244. [defaultCenter addObserverForName:kFIRAppReadyToConfigureSDKNotification
  245. object:[FIRApp class]
  246. queue:nil
  247. usingBlock:^(NSNotification *notification) {
  248. [FIRAuth authWithApp:[FIRApp appNamed:notification.userInfo[kFIRAppNameKey]]];
  249. }];
  250. // Ensures the saved user is cleared when the app is deleted.
  251. [defaultCenter addObserverForName:kFIRAppDeleteNotification
  252. object:[FIRApp class]
  253. queue:nil
  254. usingBlock:^(NSNotification *notification) {
  255. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  256. // This doesn't stop any request already issued, see b/27704535 .
  257. NSString *appName = notification.userInfo[kFIRAppNameKey];
  258. NSString *keychainServiceName = [FIRAuth keychainServiceNameForAppName:appName];
  259. if (keychainServiceName) {
  260. [self deleteKeychainServiceNameForAppName:appName];
  261. FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName];
  262. NSString *userKey = [NSString stringWithFormat:kUserKey, appName];
  263. [keychain removeDataForKey:userKey error:NULL];
  264. }
  265. dispatch_async(dispatch_get_main_queue(), ^{
  266. [[NSNotificationCenter defaultCenter]
  267. postNotificationName:FIRAuthStateDidChangeNotification
  268. object:nil];
  269. });
  270. });
  271. }];
  272. });
  273. }
  274. + (FIRAuth *)auth {
  275. FIRApp *defaultApp = [FIRApp defaultApp];
  276. if (!defaultApp) {
  277. [NSException raise:NSInternalInconsistencyException
  278. format:@"The default FIRApp instance must be configured before the default FIRAuth"
  279. @"instance can be initialized. One way to ensure that is to call "
  280. @"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App "
  281. @"Delegate's `application:didFinishLaunchingWithOptions:` "
  282. @"(`application(_:didFinishLaunchingWithOptions:)` in Swift)."];
  283. }
  284. return [self authWithApp:defaultApp];
  285. }
  286. + (FIRAuth *)authWithApp:(FIRApp *)app {
  287. return [FIRAppAssociationRegistration registeredObjectWithHost:app
  288. key:NSStringFromClass(self)
  289. creationBlock:^FIRAuth *_Nullable() {
  290. return [[FIRAuth alloc] initWithApp:app];
  291. }];
  292. }
  293. - (instancetype)initWithApp:(FIRApp *)app {
  294. [FIRAuth setKeychainServiceNameForApp:app];
  295. self = [self initWithAPIKey:app.options.APIKey appName:app.name];
  296. if (self) {
  297. _app = app;
  298. __weak FIRAuth *weakSelf = self;
  299. app.getTokenImplementation = ^(BOOL forceRefresh, FIRTokenCallback callback) {
  300. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  301. FIRAuth *strongSelf = weakSelf;
  302. // Enable token auto-refresh if not aleady enabled.
  303. if (strongSelf && !strongSelf->_autoRefreshTokens) {
  304. FIRLogInfo(kFIRLoggerAuth, @"I-AUT000002", @"Token auto-refresh enabled.");
  305. strongSelf->_autoRefreshTokens = YES;
  306. [strongSelf scheduleAutoTokenRefresh];
  307. #if TARGET_OS_IOS // TODO: Is a similar mechanism needed on macOS?
  308. strongSelf->_applicationDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter]
  309. addObserverForName:UIApplicationDidBecomeActiveNotification
  310. object:nil
  311. queue:nil
  312. usingBlock:^(NSNotification *notification) {
  313. FIRAuth *strongSelf = weakSelf;
  314. if (strongSelf) {
  315. strongSelf->_isAppInBackground = NO;
  316. if (!strongSelf->_autoRefreshScheduled) {
  317. [weakSelf scheduleAutoTokenRefresh];
  318. }
  319. }
  320. }];
  321. strongSelf->_applicationDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter]
  322. addObserverForName:UIApplicationDidEnterBackgroundNotification
  323. object:nil
  324. queue:nil
  325. usingBlock:^(NSNotification *notification) {
  326. FIRAuth *strongSelf = weakSelf;
  327. if (strongSelf) {
  328. strongSelf->_isAppInBackground = YES;
  329. }
  330. }];
  331. #endif
  332. }
  333. // Call back with 'nil' if there is no current user.
  334. if (!strongSelf || !strongSelf->_currentUser) {
  335. dispatch_async(dispatch_get_main_queue(), ^{
  336. callback(nil, nil);
  337. });
  338. return;
  339. }
  340. // Call back with current user token.
  341. [strongSelf->_currentUser internalGetTokenForcingRefresh:forceRefresh
  342. callback:^(NSString *_Nullable token,
  343. NSError *_Nullable error) {
  344. dispatch_async(dispatch_get_main_queue(), ^{
  345. callback(token, error);
  346. });
  347. }];
  348. });
  349. };
  350. app.getUIDImplementation = ^NSString *_Nullable() {
  351. __block NSString *uid;
  352. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  353. uid = [weakSelf getUID];
  354. });
  355. return uid;
  356. };
  357. #if TARGET_OS_IOS
  358. _authURLPresenter = [[FIRAuthURLPresenter alloc] init];
  359. #endif
  360. }
  361. return self;
  362. }
  363. - (instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)appName {
  364. self = [super init];
  365. if (self) {
  366. _listenerHandles = [NSMutableArray array];
  367. _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey];
  368. _firebaseAppName = [appName copy];
  369. #if TARGET_OS_IOS
  370. UIApplication *application = [UIApplication sharedApplication];
  371. // Initialize the shared FIRAuthAppDelegateProxy instance in the main thread if not already.
  372. [FIRAuthAppDelegateProxy sharedInstance];
  373. #endif
  374. // Continue with the rest of initialization in the work thread.
  375. __weak FIRAuth *weakSelf = self;
  376. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  377. // Load current user from Keychain.
  378. FIRAuth *strongSelf = weakSelf;
  379. if (!strongSelf) {
  380. return;
  381. }
  382. NSString *keychainServiceName =
  383. [FIRAuth keychainServiceNameForAppName:strongSelf->_firebaseAppName];
  384. if (keychainServiceName) {
  385. strongSelf->_keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName];
  386. }
  387. FIRUser *user;
  388. NSError *error;
  389. if ([strongSelf getUser:&user error:&error]) {
  390. [strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error];
  391. _lastNotifiedUserToken = user.rawAccessToken;
  392. } else {
  393. FIRLogError(kFIRLoggerAuth, @"I-AUT000001",
  394. @"Error loading saved user when starting up: %@", error);
  395. }
  396. #if TARGET_OS_IOS
  397. // Initialize for phone number auth.
  398. strongSelf->_tokenManager =
  399. [[FIRAuthAPNSTokenManager alloc] initWithApplication:application];
  400. strongSelf->_appCredentialManager =
  401. [[FIRAuthAppCredentialManager alloc] initWithKeychain:strongSelf->_keychain];
  402. strongSelf->_notificationManager = [[FIRAuthNotificationManager alloc]
  403. initWithApplication:application
  404. appCredentialManager:strongSelf->_appCredentialManager];
  405. [[FIRAuthAppDelegateProxy sharedInstance] addHandler:strongSelf];
  406. #endif
  407. });
  408. }
  409. return self;
  410. }
  411. - (void)dealloc {
  412. @synchronized (self) {
  413. NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  414. while (_listenerHandles.count != 0) {
  415. FIRAuthStateDidChangeListenerHandle handleToRemove = _listenerHandles.lastObject;
  416. [defaultCenter removeObserver:handleToRemove];
  417. [_listenerHandles removeLastObject];
  418. }
  419. #if TARGET_OS_IOS
  420. [defaultCenter removeObserver:_applicationDidBecomeActiveObserver
  421. name:UIApplicationDidBecomeActiveNotification
  422. object:nil];
  423. [defaultCenter removeObserver:_applicationDidEnterBackgroundObserver
  424. name:UIApplicationDidEnterBackgroundNotification
  425. object:nil];
  426. #endif
  427. }
  428. }
  429. #pragma mark - Public API
  430. - (FIRUser *)currentUser {
  431. __block FIRUser *result;
  432. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  433. result = _currentUser;
  434. });
  435. return result;
  436. }
  437. - (void)fetchProvidersForEmail:(NSString *)email
  438. completion:(FIRProviderQueryCallback)completion {
  439. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  440. FIRCreateAuthURIRequest *request =
  441. [[FIRCreateAuthURIRequest alloc] initWithIdentifier:email
  442. continueURI:@"http://www.google.com/"
  443. requestConfiguration:_requestConfiguration];
  444. [FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response,
  445. NSError *_Nullable error) {
  446. if (completion) {
  447. dispatch_async(dispatch_get_main_queue(), ^{
  448. completion(response.allProviders, error);
  449. });
  450. }
  451. }];
  452. });
  453. }
  454. - (void)signInWithEmail:(NSString *)email
  455. password:(NSString *)password
  456. completion:(FIRAuthResultCallback)completion {
  457. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  458. FIRAuthResultCallback decoratedCallback =
  459. [self signInFlowAuthResultCallbackByDecoratingCallback:completion];
  460. [self internalSignInAndRetrieveDataWithEmail:email
  461. password:password
  462. completion:^(FIRAuthDataResult *_Nullable authResult,
  463. NSError *_Nullable error) {
  464. decoratedCallback(authResult.user, error);
  465. }];
  466. });
  467. }
  468. /** @fn signInWithEmail:password:callback:
  469. @brief Signs in using an email address and password.
  470. @param email The user's email address.
  471. @param password The user's password.
  472. @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
  473. asynchronously on the global auth work queue in the future.
  474. @remarks This is the internal counterpart of this method, which uses a callback that does not
  475. update the current user.
  476. */
  477. - (void)signInWithEmail:(NSString *)email
  478. password:(NSString *)password
  479. callback:(FIRAuthResultCallback)callback {
  480. FIRVerifyPasswordRequest *request =
  481. [[FIRVerifyPasswordRequest alloc] initWithEmail:email
  482. password:password
  483. requestConfiguration:_requestConfiguration];
  484. if (![request.password length]) {
  485. callback(nil, [FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]);
  486. return;
  487. }
  488. [FIRAuthBackend verifyPassword:request
  489. callback:^(FIRVerifyPasswordResponse *_Nullable response,
  490. NSError *_Nullable error) {
  491. if (error) {
  492. callback(nil, error);
  493. return;
  494. }
  495. [self completeSignInWithAccessToken:response.IDToken
  496. accessTokenExpirationDate:response.approximateExpirationDate
  497. refreshToken:response.refreshToken
  498. anonymous:NO
  499. callback:callback];
  500. }];
  501. }
  502. - (void)signInAndRetrieveDataWithEmail:(NSString *)email
  503. password:(NSString *)password
  504. completion:(FIRAuthDataResultCallback)completion {
  505. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  506. FIRAuthDataResultCallback decoratedCallback =
  507. [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
  508. [self internalSignInAndRetrieveDataWithEmail:email
  509. password:password
  510. completion:decoratedCallback];
  511. });
  512. }
  513. /** @fn internalSignInAndRetrieveDataWithEmail:password:callback:
  514. @brief Signs in using an email address and password.
  515. @param email The user's email address.
  516. @param password The user's password.
  517. @param completion A block which is invoked when the sign in finishes (or is cancelled.) Invoked
  518. asynchronously on the global auth work queue in the future.
  519. @remarks This is the internal counterpart of this method, which uses a callback that does not
  520. update the current user.
  521. */
  522. - (void)internalSignInAndRetrieveDataWithEmail:(NSString *)email
  523. password:(NSString *)password
  524. completion:(FIRAuthDataResultCallback)completion {
  525. FIREmailPasswordAuthCredential *credentail =
  526. [[FIREmailPasswordAuthCredential alloc] initWithEmail:email password:password];
  527. [self internalSignInAndRetrieveDataWithCredential:credentail
  528. isReauthentication:NO
  529. callback:completion];
  530. }
  531. - (void)signInWithCredential:(FIRAuthCredential *)credential
  532. completion:(FIRAuthResultCallback)completion {
  533. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  534. FIRAuthResultCallback callback =
  535. [self signInFlowAuthResultCallbackByDecoratingCallback:completion];
  536. [self internalSignInWithCredential:credential callback:callback];
  537. });
  538. }
  539. - (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
  540. completion:(nullable FIRAuthDataResultCallback)completion {
  541. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  542. FIRAuthDataResultCallback callback =
  543. [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
  544. [self internalSignInAndRetrieveDataWithCredential:credential
  545. isReauthentication:NO
  546. callback:callback];
  547. });
  548. }
  549. - (void)internalSignInWithCredential:(FIRAuthCredential *)credential
  550. callback:(FIRAuthResultCallback)callback {
  551. [self internalSignInAndRetrieveDataWithCredential:credential
  552. isReauthentication:NO
  553. callback:^(FIRAuthDataResult *_Nullable authResult,
  554. NSError *_Nullable error) {
  555. callback(authResult.user, error);
  556. }];
  557. }
  558. - (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
  559. isReauthentication:(BOOL)isReauthentication
  560. callback:(nullable FIRAuthDataResultCallback)callback {
  561. if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
  562. // Special case for email/password credentials
  563. FIREmailPasswordAuthCredential *emailPasswordCredential =
  564. (FIREmailPasswordAuthCredential *)credential;
  565. [self signInWithEmail:emailPasswordCredential.email
  566. password:emailPasswordCredential.password
  567. callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  568. if (callback) {
  569. if (error) {
  570. callback(nil, error);
  571. return;
  572. }
  573. FIRAdditionalUserInfo *additionalUserInfo =
  574. [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
  575. profile:nil
  576. username:nil
  577. isNewUser:NO];
  578. FIRAuthDataResult *result = [[FIRAuthDataResult alloc] initWithUser:user
  579. additionalUserInfo:additionalUserInfo];
  580. callback(result, nil);
  581. }
  582. }];
  583. return;
  584. }
  585. #if TARGET_OS_IOS
  586. if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) {
  587. // Special case for phone auth credentials
  588. FIRPhoneAuthCredential *phoneCredential = (FIRPhoneAuthCredential *)credential;
  589. FIRAuthOperationType operation =
  590. isReauthentication ? FIRAuthOperationTypeReauth : FIRAuthOperationTypeSignUpOrSignIn;
  591. [self signInWithPhoneCredential:phoneCredential
  592. operation:operation
  593. callback:^(FIRUser *_Nullable user,
  594. NSError *_Nullable error) {
  595. if (callback) {
  596. FIRAuthDataResult *result = user ?
  597. [[FIRAuthDataResult alloc] initWithUser:user additionalUserInfo:nil] : nil;
  598. callback(result, error);
  599. }
  600. }];
  601. return;
  602. }
  603. #endif
  604. FIRVerifyAssertionRequest *request =
  605. [[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider
  606. requestConfiguration:_requestConfiguration];
  607. request.autoCreate = !isReauthentication;
  608. [credential prepareVerifyAssertionRequest:request];
  609. [FIRAuthBackend verifyAssertion:request
  610. callback:^(FIRVerifyAssertionResponse *response, NSError *error) {
  611. if (error) {
  612. if (callback) {
  613. callback(nil, error);
  614. }
  615. return;
  616. }
  617. if (response.needConfirmation) {
  618. if (callback) {
  619. NSString *email = response.email;
  620. callback(nil, [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:email]);
  621. }
  622. return;
  623. }
  624. if (!response.providerID.length) {
  625. if (callback) {
  626. callback(nil, [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:response]);
  627. }
  628. return;
  629. }
  630. [self completeSignInWithAccessToken:response.IDToken
  631. accessTokenExpirationDate:response.approximateExpirationDate
  632. refreshToken:response.refreshToken
  633. anonymous:NO
  634. callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  635. if (callback) {
  636. FIRAdditionalUserInfo *additionalUserInfo =
  637. [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
  638. FIRAuthDataResult *result = user ?
  639. [[FIRAuthDataResult alloc] initWithUser:user
  640. additionalUserInfo:additionalUserInfo] : nil;
  641. callback(result, error);
  642. }
  643. }];
  644. }];
  645. }
  646. - (void)signInWithCredential:(FIRAuthCredential *)credential
  647. callback:(FIRAuthResultCallback)callback {
  648. [self signInAndRetrieveDataWithCredential:credential
  649. completion:^(FIRAuthDataResult *_Nullable authResult,
  650. NSError *_Nullable error) {
  651. callback(authResult.user, error);
  652. }];
  653. }
  654. - (void)signInAnonymouslyAndRetrieveDataWithCompletion:(FIRAuthDataResultCallback)completion {
  655. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  656. FIRAuthDataResultCallback decoratedCallback =
  657. [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
  658. if (_currentUser.anonymous) {
  659. FIRAdditionalUserInfo *additionalUserInfo =
  660. [[FIRAdditionalUserInfo alloc] initWithProviderID:nil
  661. profile:nil
  662. username:nil
  663. isNewUser:NO];
  664. FIRAuthDataResult *authDataResult =
  665. [[FIRAuthDataResult alloc] initWithUser:_currentUser
  666. additionalUserInfo:additionalUserInfo];
  667. decoratedCallback(authDataResult, nil);
  668. return;
  669. }
  670. [self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response,
  671. NSError *_Nullable error) {
  672. if (error) {
  673. decoratedCallback(nil, error);
  674. return;
  675. }
  676. [self completeSignInWithAccessToken:response.IDToken
  677. accessTokenExpirationDate:response.approximateExpirationDate
  678. refreshToken:response.refreshToken
  679. anonymous:YES
  680. callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  681. FIRAdditionalUserInfo *additionalUserInfo =
  682. [[FIRAdditionalUserInfo alloc] initWithProviderID:nil
  683. profile:nil
  684. username:nil
  685. isNewUser:YES];
  686. FIRAuthDataResult *authDataResult =
  687. [[FIRAuthDataResult alloc] initWithUser:user
  688. additionalUserInfo:additionalUserInfo];
  689. decoratedCallback(authDataResult, nil);
  690. }];
  691. }];
  692. });
  693. }
  694. - (void)signInAnonymouslyWithCompletion:(FIRAuthResultCallback)completion {
  695. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  696. FIRAuthResultCallback decoratedCallback =
  697. [self signInFlowAuthResultCallbackByDecoratingCallback:completion];
  698. if (_currentUser.anonymous) {
  699. decoratedCallback(_currentUser, nil);
  700. return;
  701. }
  702. [self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response,
  703. NSError *_Nullable error) {
  704. if (error) {
  705. decoratedCallback(nil, error);
  706. return;
  707. }
  708. [self completeSignInWithAccessToken:response.IDToken
  709. accessTokenExpirationDate:response.approximateExpirationDate
  710. refreshToken:response.refreshToken
  711. anonymous:YES
  712. callback:decoratedCallback];
  713. }];
  714. });
  715. }
  716. - (void)signInWithCustomToken:(NSString *)token
  717. completion:(nullable FIRAuthResultCallback)completion {
  718. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  719. FIRAuthResultCallback decoratedCallback =
  720. [self signInFlowAuthResultCallbackByDecoratingCallback:completion];
  721. [self internalSignInAndRetrieveDataWithCustomToken:token
  722. completion:^(FIRAuthDataResult *_Nullable authResult,
  723. NSError *_Nullable error) {
  724. decoratedCallback(authResult.user, error);
  725. }];
  726. });
  727. }
  728. - (void)signInAndRetrieveDataWithCustomToken:(NSString *)token
  729. completion:(nullable FIRAuthDataResultCallback)completion {
  730. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  731. FIRAuthDataResultCallback decoratedCallback =
  732. [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
  733. [self internalSignInAndRetrieveDataWithCustomToken:token completion:decoratedCallback];
  734. });
  735. }
  736. - (void)createUserWithEmail:(NSString *)email
  737. password:(NSString *)password
  738. completion:(nullable FIRAuthResultCallback)completion {
  739. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  740. FIRAuthResultCallback decoratedCallback =
  741. [self signInFlowAuthResultCallbackByDecoratingCallback:completion];
  742. [self internalCreateUserWithEmail:email
  743. password:password
  744. completion:^(FIRSignUpNewUserResponse *_Nullable response,
  745. NSError *_Nullable error) {
  746. if (error) {
  747. decoratedCallback(nil, error);
  748. return;
  749. }
  750. [self completeSignInWithAccessToken:response.IDToken
  751. accessTokenExpirationDate:response.approximateExpirationDate
  752. refreshToken:response.refreshToken
  753. anonymous:NO
  754. callback:decoratedCallback];
  755. }];
  756. });
  757. }
  758. - (void)createUserAndRetrieveDataWithEmail:(NSString *)email
  759. password:(NSString *)password
  760. completion:(FIRAuthDataResultCallback)completion {
  761. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  762. FIRAuthDataResultCallback decoratedCallback =
  763. [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
  764. [self internalCreateUserWithEmail:email
  765. password:password
  766. completion:^(FIRSignUpNewUserResponse *_Nullable response,
  767. NSError *_Nullable error) {
  768. if (error) {
  769. decoratedCallback(nil, error);
  770. return;
  771. }
  772. [self completeSignInWithAccessToken:response.IDToken
  773. accessTokenExpirationDate:response.approximateExpirationDate
  774. refreshToken:response.refreshToken
  775. anonymous:NO
  776. callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  777. FIRAdditionalUserInfo *additionalUserInfo =
  778. [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
  779. profile:nil
  780. username:nil
  781. isNewUser:YES];
  782. FIRAuthDataResult *authDataResult =
  783. [[FIRAuthDataResult alloc] initWithUser:user
  784. additionalUserInfo:additionalUserInfo];
  785. decoratedCallback(authDataResult, nil);
  786. }];
  787. }];
  788. });
  789. }
  790. - (void)confirmPasswordResetWithCode:(NSString *)code
  791. newPassword:(NSString *)newPassword
  792. completion:(FIRConfirmPasswordResetCallback)completion {
  793. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  794. FIRResetPasswordRequest *request =
  795. [[FIRResetPasswordRequest alloc] initWithOobCode:code
  796. newPassword:newPassword
  797. requestConfiguration:_requestConfiguration];
  798. [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
  799. NSError *_Nullable error) {
  800. if (completion) {
  801. dispatch_async(dispatch_get_main_queue(), ^{
  802. if (error) {
  803. completion(error);
  804. return;
  805. }
  806. completion(nil);
  807. });
  808. }
  809. }];
  810. });
  811. }
  812. - (void)checkActionCode:(NSString *)code completion:(FIRCheckActionCodeCallBack)completion {
  813. dispatch_async(FIRAuthGlobalWorkQueue(), ^ {
  814. FIRResetPasswordRequest *request =
  815. [[FIRResetPasswordRequest alloc] initWithOobCode:code
  816. newPassword:nil
  817. requestConfiguration:_requestConfiguration];
  818. [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
  819. NSError *_Nullable error) {
  820. if (completion) {
  821. if (error) {
  822. dispatch_async(dispatch_get_main_queue(), ^{
  823. completion(nil, error);
  824. });
  825. return;
  826. }
  827. FIRActionCodeOperation operation =
  828. [FIRActionCodeInfo actionCodeOperationForRequestType:response.requestType];
  829. FIRActionCodeInfo *actionCodeInfo =
  830. [[FIRActionCodeInfo alloc] initWithOperation:operation
  831. email:response.email
  832. newEmail:response.verifiedEmail];
  833. dispatch_async(dispatch_get_main_queue(), ^{
  834. completion(actionCodeInfo, nil);
  835. });
  836. }
  837. }];
  838. });
  839. }
  840. - (void)verifyPasswordResetCode:(NSString *)code
  841. completion:(FIRVerifyPasswordResetCodeCallback)completion {
  842. [self checkActionCode:code completion:^(FIRActionCodeInfo *_Nullable info,
  843. NSError *_Nullable error) {
  844. if (completion) {
  845. if (error) {
  846. completion(nil, error);
  847. return;
  848. }
  849. completion([info dataForKey:FIRActionCodeEmailKey], nil);
  850. }
  851. }];
  852. }
  853. - (void)applyActionCode:(NSString *)code completion:(FIRApplyActionCodeCallback)completion {
  854. dispatch_async(FIRAuthGlobalWorkQueue(), ^ {
  855. FIRSetAccountInfoRequest *request =
  856. [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:_requestConfiguration];
  857. request.OOBCode = code;
  858. [FIRAuthBackend setAccountInfo:request callback:^(FIRSetAccountInfoResponse *_Nullable response,
  859. NSError *_Nullable error) {
  860. if (completion) {
  861. dispatch_async(dispatch_get_main_queue(), ^{
  862. completion(error);
  863. });
  864. }
  865. }];
  866. });
  867. }
  868. - (void)sendPasswordResetWithEmail:(NSString *)email
  869. completion:(nullable FIRSendPasswordResetCallback)completion {
  870. [self sendPasswordResetWithNullableActionCodeSettings:nil email:email completion:completion];
  871. }
  872. - (void)sendPasswordResetWithEmail:(NSString *)email
  873. actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
  874. completion:(nullable FIRSendPasswordResetCallback)completion {
  875. [self sendPasswordResetWithNullableActionCodeSettings:actionCodeSettings
  876. email:email
  877. completion:completion];
  878. }
  879. /** @fn sendPasswordResetWithNullableActionCodeSettings:actionCodeSetting:email:completion:
  880. @brief Initiates a password reset for the given email address and @FIRActionCodeSettings object.
  881. @param actionCodeSettings Optionally, An @c FIRActionCodeSettings object containing settings
  882. related to the handling action codes.
  883. @param email The email address of the user.
  884. @param completion Optionally; a block which is invoked when the request finishes. Invoked
  885. asynchronously on the main thread in the future.
  886. */
  887. - (void)sendPasswordResetWithNullableActionCodeSettings:(nullable FIRActionCodeSettings *)
  888. actionCodeSettings
  889. email:(NSString *)email
  890. completion:(nullable FIRSendPasswordResetCallback)
  891. completion {
  892. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  893. if (!email) {
  894. [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
  895. kMissingEmailInvalidParameterExceptionReason];
  896. }
  897. FIRGetOOBConfirmationCodeRequest *request =
  898. [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:email
  899. actionCodeSettings:actionCodeSettings
  900. requestConfiguration:_requestConfiguration];
  901. [FIRAuthBackend getOOBConfirmationCode:request
  902. callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
  903. NSError *_Nullable error) {
  904. if (completion) {
  905. dispatch_async(dispatch_get_main_queue(), ^{
  906. completion(error);
  907. });
  908. }
  909. }];
  910. });
  911. }
  912. - (BOOL)signOut:(NSError *_Nullable __autoreleasing *_Nullable)error {
  913. __block BOOL result = YES;
  914. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  915. if (!_currentUser) {
  916. return;
  917. }
  918. result = [self updateCurrentUser:nil byForce:NO savingToDisk:YES error:error];
  919. });
  920. return result;
  921. }
  922. - (BOOL)signOutByForceWithUserID:(NSString *)userID error:(NSError *_Nullable *_Nullable)error {
  923. if (_currentUser.uid != userID) {
  924. return YES;
  925. }
  926. return [self updateCurrentUser:nil byForce:YES savingToDisk:YES error:error];
  927. }
  928. - (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener:
  929. (FIRAuthStateDidChangeListenerBlock)listener {
  930. __block BOOL firstInvocation = YES;
  931. __block NSString *previousUserID;
  932. return [self addIDTokenDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
  933. BOOL shouldCallListener = firstInvocation ||
  934. !(previousUserID == user.uid || [previousUserID isEqualToString:user.uid]);
  935. firstInvocation = NO;
  936. previousUserID = [user.uid copy];
  937. if (shouldCallListener) {
  938. listener(auth, user);
  939. }
  940. }];
  941. }
  942. - (void)removeAuthStateDidChangeListener:(FIRAuthStateDidChangeListenerHandle)listenerHandle {
  943. [self removeIDTokenDidChangeListener:listenerHandle];
  944. }
  945. - (FIRIDTokenDidChangeListenerHandle)addIDTokenDidChangeListener:
  946. (FIRIDTokenDidChangeListenerBlock)listener {
  947. if (!listener) {
  948. [NSException raise:NSInvalidArgumentException format:@"listener must not be nil."];
  949. return nil;
  950. }
  951. FIRAuthStateDidChangeListenerHandle handle;
  952. NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter];
  953. handle = [notifications addObserverForName:FIRAuthStateDidChangeNotification
  954. object:self
  955. queue:[NSOperationQueue mainQueue]
  956. usingBlock:^(NSNotification *_Nonnull notification) {
  957. FIRAuth *auth = notification.object;
  958. listener(auth, auth.currentUser);
  959. }];
  960. @synchronized (self) {
  961. [_listenerHandles addObject:handle];
  962. }
  963. dispatch_async(dispatch_get_main_queue(), ^{
  964. listener(self, self.currentUser);
  965. });
  966. return handle;
  967. }
  968. - (void)removeIDTokenDidChangeListener:(FIRIDTokenDidChangeListenerHandle)listenerHandle {
  969. [[NSNotificationCenter defaultCenter] removeObserver:listenerHandle];
  970. @synchronized (self) {
  971. [_listenerHandles removeObject:listenerHandle];
  972. }
  973. }
  974. - (void)useAppLanguage {
  975. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  976. _requestConfiguration.languageCode = [NSBundle mainBundle].preferredLocalizations.firstObject;
  977. });
  978. }
  979. - (nullable NSString *)languageCode {
  980. return _requestConfiguration.languageCode;
  981. }
  982. - (void)setLanguageCode:(nullable NSString *)languageCode {
  983. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  984. _requestConfiguration.languageCode = [languageCode copy];
  985. });
  986. }
  987. - (NSString *)additionalFrameworkMarker {
  988. return _requestConfiguration.additionalFrameworkMarker;
  989. }
  990. - (void)setAdditionalFrameworkMarker:(NSString *)additionalFrameworkMarker {
  991. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  992. _requestConfiguration.additionalFrameworkMarker = [additionalFrameworkMarker copy];
  993. });
  994. }
  995. #if TARGET_OS_IOS
  996. - (NSData *)APNSToken {
  997. __block NSData *result = nil;
  998. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  999. result = _tokenManager.token.data;
  1000. });
  1001. return result;
  1002. }
  1003. - (void)setAPNSToken:(NSData *)APNSToken {
  1004. [self setAPNSToken:APNSToken type:FIRAuthAPNSTokenTypeUnknown];
  1005. }
  1006. - (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type {
  1007. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1008. _tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type];
  1009. });
  1010. }
  1011. - (void)handleAPNSTokenError:(NSError *)error {
  1012. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1013. [_tokenManager cancelWithError:error];
  1014. });
  1015. }
  1016. - (BOOL)canHandleNotification:(NSDictionary *)userInfo {
  1017. __block BOOL result = NO;
  1018. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1019. result = [_notificationManager canHandleNotification:userInfo];
  1020. });
  1021. return result;
  1022. }
  1023. - (BOOL)canHandleURL:(NSURL *)URL {
  1024. __block BOOL result = NO;
  1025. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1026. result = [_authURLPresenter canHandleURL:URL];
  1027. });
  1028. return result;
  1029. }
  1030. #endif
  1031. #pragma mark - Internal Methods
  1032. #if TARGET_OS_IOS
  1033. /** @fn signInWithPhoneCredential:callback:
  1034. @brief Signs in using a phone credential.
  1035. @param credential The Phone Auth credential used to sign in.
  1036. @param operation The type of operation for which this sign-in attempt is initiated.
  1037. @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
  1038. asynchronously on the global auth work queue in the future.
  1039. */
  1040. - (void)signInWithPhoneCredential:(FIRPhoneAuthCredential *)credential
  1041. operation:(FIRAuthOperationType)operation
  1042. callback:(FIRAuthResultCallback)callback {
  1043. if (credential.temporaryProof.length && credential.phoneNumber.length) {
  1044. FIRVerifyPhoneNumberRequest *request =
  1045. [[FIRVerifyPhoneNumberRequest alloc] initWithTemporaryProof:credential.temporaryProof
  1046. phoneNumber:credential.phoneNumber
  1047. operation:operation
  1048. requestConfiguration:_requestConfiguration];
  1049. [self phoneNumberSignInWithRequest:request callback:callback];
  1050. return;
  1051. }
  1052. if (!credential.verificationID.length) {
  1053. callback(nil, [FIRAuthErrorUtils missingVerificationIDErrorWithMessage:nil]);
  1054. return;
  1055. }
  1056. if (!credential.verificationCode.length) {
  1057. callback(nil, [FIRAuthErrorUtils missingVerificationCodeErrorWithMessage:nil]);
  1058. return;
  1059. }
  1060. FIRVerifyPhoneNumberRequest *request =
  1061. [[FIRVerifyPhoneNumberRequest alloc]initWithVerificationID:credential.verificationID
  1062. verificationCode:credential.verificationCode
  1063. operation:operation
  1064. requestConfiguration:_requestConfiguration];
  1065. [self phoneNumberSignInWithRequest:request callback:callback];
  1066. }
  1067. /** @fn phoneNumberSignInWithVerificationID:pasverificationCodesword:callback:
  1068. @brief Signs in using a FIRVerifyPhoneNumberRequest object.
  1069. @param request THe FIRVerifyPhoneNumberRequest request object.
  1070. @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
  1071. asynchronously on the global auth work queue in the future.
  1072. */
  1073. - (void)phoneNumberSignInWithRequest:(FIRVerifyPhoneNumberRequest *)request
  1074. callback:(FIRAuthResultCallback)callback {
  1075. [FIRAuthBackend verifyPhoneNumber:request
  1076. callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
  1077. NSError *_Nullable error) {
  1078. if (error) {
  1079. callback(nil, error);
  1080. return;
  1081. }
  1082. [self completeSignInWithAccessToken:response.IDToken
  1083. accessTokenExpirationDate:response.approximateExpirationDate
  1084. refreshToken:response.refreshToken
  1085. anonymous:NO
  1086. callback:callback];
  1087. }];
  1088. }
  1089. #endif
  1090. /** @fn internalSignInAndRetrieveDataWithCustomToken:completion:
  1091. @brief Signs in a Firebase user given a custom token.
  1092. @param token A self-signed custom auth token.
  1093. @param completion A block which is invoked when the custom token sign in request completes.
  1094. */
  1095. - (void)internalSignInAndRetrieveDataWithCustomToken:(NSString *)token
  1096. completion:(nullable FIRAuthDataResultCallback)
  1097. completion {
  1098. FIRVerifyCustomTokenRequest *request =
  1099. [[FIRVerifyCustomTokenRequest alloc] initWithToken:token
  1100. requestConfiguration:_requestConfiguration];
  1101. [FIRAuthBackend verifyCustomToken:request
  1102. callback:^(FIRVerifyCustomTokenResponse *_Nullable response,
  1103. NSError *_Nullable error) {
  1104. if (error) {
  1105. if (completion) {
  1106. completion(nil, error);
  1107. return;
  1108. }
  1109. }
  1110. [self completeSignInWithAccessToken:response.IDToken
  1111. accessTokenExpirationDate:response.approximateExpirationDate
  1112. refreshToken:response.refreshToken
  1113. anonymous:NO
  1114. callback:^(FIRUser *_Nullable user,
  1115. NSError *_Nullable error) {
  1116. if (error) {
  1117. if (completion) {
  1118. completion(nil, error);
  1119. }
  1120. return;
  1121. }
  1122. FIRAdditionalUserInfo *additonalUserInfo =
  1123. [[FIRAdditionalUserInfo alloc] initWithProviderID:nil
  1124. profile:nil
  1125. username:nil
  1126. isNewUser:response.isNewUser];
  1127. FIRAuthDataResult *result =
  1128. [[FIRAuthDataResult alloc] initWithUser:user additionalUserInfo:additonalUserInfo];
  1129. if (completion) {
  1130. completion(result, nil);
  1131. }
  1132. }];
  1133. }];
  1134. }
  1135. /** @fn internalCreateUserWithEmail:password:completion:
  1136. @brief Makes a backend request attempting to create a new Firebase user given an email address
  1137. and password.
  1138. @param email The email address used to create the new Firebase user.
  1139. @param password The password used to create the new Firebase user.
  1140. @param completion Optionally; a block which is invoked when the request finishes.
  1141. */
  1142. - (void)internalCreateUserWithEmail:(NSString *)email
  1143. password:(NSString *)password
  1144. completion:(nullable FIRSignupNewUserCallback)completion {
  1145. FIRSignUpNewUserRequest *request =
  1146. [[FIRSignUpNewUserRequest alloc] initWithEmail:email
  1147. password:password
  1148. displayName:nil
  1149. requestConfiguration:_requestConfiguration];
  1150. if (![request.password length]) {
  1151. completion(nil, [FIRAuthErrorUtils
  1152. weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]);
  1153. return;
  1154. }
  1155. if (![request.email length]) {
  1156. completion(nil, [FIRAuthErrorUtils missingEmailErrorWithMessage:nil]);
  1157. return;
  1158. }
  1159. [FIRAuthBackend signUpNewUser:request callback:completion];
  1160. }
  1161. /** @fn internalSignInAnonymouslyWithCompletion:
  1162. @param completion A block which is invoked when the anonymous sign in request completes.
  1163. */
  1164. - (void)internalSignInAnonymouslyWithCompletion:(FIRSignupNewUserCallback)completion {
  1165. FIRSignUpNewUserRequest *request =
  1166. [[FIRSignUpNewUserRequest alloc]initWithRequestConfiguration:_requestConfiguration];
  1167. [FIRAuthBackend signUpNewUser:request
  1168. callback:completion];
  1169. }
  1170. /** @fn possiblyPostAuthStateChangeNotification
  1171. @brief Posts the auth state change notificaton if current user's token has been changed.
  1172. */
  1173. - (void)possiblyPostAuthStateChangeNotification {
  1174. NSString *token = _currentUser.rawAccessToken;
  1175. if (_lastNotifiedUserToken == token || [_lastNotifiedUserToken isEqualToString:token]) {
  1176. return;
  1177. }
  1178. _lastNotifiedUserToken = token;
  1179. if (_autoRefreshTokens) {
  1180. // Shedule new refresh task after successful attempt.
  1181. [self scheduleAutoTokenRefresh];
  1182. }
  1183. NSMutableDictionary *internalNotificationParameters = [NSMutableDictionary dictionary];
  1184. if (self.app) {
  1185. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = self.app;
  1186. }
  1187. if (token.length) {
  1188. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token;
  1189. }
  1190. internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = _currentUser.uid;
  1191. NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter];
  1192. dispatch_async(dispatch_get_main_queue(), ^{
  1193. [notifications postNotificationName:FIRAuthStateDidChangeInternalNotification
  1194. object:self
  1195. userInfo:internalNotificationParameters];
  1196. [notifications postNotificationName:FIRAuthStateDidChangeNotification
  1197. object:self];
  1198. });
  1199. }
  1200. - (BOOL)updateKeychainWithUser:(FIRUser *)user error:(NSError *_Nullable *_Nullable)error {
  1201. if (user != _currentUser) {
  1202. // No-op if the user is no longer signed in. This is not considered an error as we don't check
  1203. // whether the user is still current on other callbacks of user operations either.
  1204. return YES;
  1205. }
  1206. if ([self saveUser:user error:error]) {
  1207. [self possiblyPostAuthStateChangeNotification];
  1208. return YES;
  1209. }
  1210. return NO;
  1211. }
  1212. /** @fn setKeychainServiceNameForApp
  1213. @brief Sets the keychain service name global data for the particular app.
  1214. @param app The Firebase app to set keychain service name for.
  1215. */
  1216. + (void)setKeychainServiceNameForApp:(FIRApp *)app {
  1217. @synchronized (self) {
  1218. gKeychainServiceNameForAppName[app.name] =
  1219. [@"firebase_auth_" stringByAppendingString:app.options.googleAppID];
  1220. }
  1221. }
  1222. /** @fn keychainServiceNameForAppName:
  1223. @brief Gets the keychain service name global data for the particular app by name.
  1224. @param appName The name of the Firebase app to get keychain service name for.
  1225. */
  1226. + (NSString *)keychainServiceNameForAppName:(NSString *)appName {
  1227. @synchronized (self) {
  1228. return gKeychainServiceNameForAppName[appName];
  1229. }
  1230. }
  1231. /** @fn deleteKeychainServiceNameForAppName:
  1232. @brief Deletes the keychain service name global data for the particular app by name.
  1233. @param appName The name of the Firebase app to delete keychain service name for.
  1234. */
  1235. + (void)deleteKeychainServiceNameForAppName:(NSString *)appName {
  1236. @synchronized (self) {
  1237. [gKeychainServiceNameForAppName removeObjectForKey:appName];
  1238. }
  1239. }
  1240. /** @fn scheduleAutoTokenRefreshWithDelay:
  1241. @brief Schedules a task to automatically refresh tokens on the current user. The token refresh
  1242. is scheduled 5 minutes before the scheduled expiration time.
  1243. @remarks If the token expires in less than 5 minutes, schedule the token refresh immediately.
  1244. */
  1245. - (void)scheduleAutoTokenRefresh {
  1246. NSTimeInterval tokenExpirationInterval =
  1247. [_currentUser.accessTokenExpirationDate timeIntervalSinceNow] - kTokenRefreshHeadStart;
  1248. [self scheduleAutoTokenRefreshWithDelay:MAX(tokenExpirationInterval, 0) retry:NO];
  1249. }
  1250. /** @fn scheduleAutoTokenRefreshWithDelay:
  1251. @brief Schedules a task to automatically refresh tokens on the current user.
  1252. @param delay The delay in seconds after which the token refresh task should be scheduled to be
  1253. executed.
  1254. @param retry Flag to determine whether the invocation is a retry attempt or not.
  1255. */
  1256. - (void)scheduleAutoTokenRefreshWithDelay:(NSTimeInterval)delay retry:(BOOL)retry {
  1257. NSString *accessToken = _currentUser.rawAccessToken;
  1258. if (!accessToken) {
  1259. return;
  1260. }
  1261. if (retry) {
  1262. FIRLogInfo(kFIRLoggerAuth, @"I-AUT000003",
  1263. @"Token auto-refresh re-scheduled in %02d:%02d "
  1264. @"because of error on previous refresh attempt.",
  1265. (int)ceil(delay) / 60, (int)ceil(delay) % 60);
  1266. } else {
  1267. FIRLogInfo(kFIRLoggerAuth, @"I-AUT000004",
  1268. @"Token auto-refresh scheduled in %02d:%02d for the new token.",
  1269. (int)ceil(delay) / 60, (int)ceil(delay) % 60);
  1270. }
  1271. _autoRefreshScheduled = YES;
  1272. __weak FIRAuth *weakSelf = self;
  1273. [[FIRAuthDispatcher sharedInstance] dispatchAfterDelay:delay
  1274. queue:FIRAuthGlobalWorkQueue()
  1275. task:^(void) {
  1276. FIRAuth *strongSelf = weakSelf;
  1277. if (!strongSelf) {
  1278. return;
  1279. }
  1280. if (![strongSelf->_currentUser.rawAccessToken isEqualToString:accessToken]) {
  1281. // Another auto refresh must have been scheduled, so keep _autoRefreshScheduled unchanged.
  1282. return;
  1283. }
  1284. strongSelf->_autoRefreshScheduled = NO;
  1285. if (strongSelf->_isAppInBackground) {
  1286. return;
  1287. }
  1288. NSString *uid = strongSelf->_currentUser.uid;
  1289. [strongSelf->_currentUser internalGetTokenForcingRefresh:YES
  1290. callback:^(NSString *_Nullable token,
  1291. NSError *_Nullable error) {
  1292. if (![strongSelf->_currentUser.uid isEqualToString:uid]) {
  1293. return;
  1294. }
  1295. if (error) {
  1296. // Kicks off exponential back off logic to retry failed attempt. Starts with one minute
  1297. // delay (60 seconds) if this is the first failed attempt.
  1298. NSTimeInterval rescheduleDelay;
  1299. if (retry) {
  1300. rescheduleDelay = MIN(delay * 2, kMaxWaitTimeForBackoff);
  1301. } else {
  1302. rescheduleDelay = 60;
  1303. }
  1304. [strongSelf scheduleAutoTokenRefreshWithDelay:rescheduleDelay retry:YES];
  1305. }
  1306. }];
  1307. }];
  1308. }
  1309. #pragma mark -
  1310. /** @fn completeSignInWithTokenService:callback:
  1311. @brief Completes a sign-in flow once we have access and refresh tokens for the user.
  1312. @param accessToken The STS access token.
  1313. @param accessTokenExpirationDate The approximate expiration date of the access token.
  1314. @param refreshToken The STS refresh token.
  1315. @param anonymous Whether or not the user is anonymous.
  1316. @param callback Called when the user has been signed in or when an error occurred. Invoked
  1317. asynchronously on the global auth work queue in the future.
  1318. */
  1319. - (void)completeSignInWithAccessToken:(NSString *)accessToken
  1320. accessTokenExpirationDate:(NSDate *)accessTokenExpirationDate
  1321. refreshToken:(NSString *)refreshToken
  1322. anonymous:(BOOL)anonymous
  1323. callback:(FIRAuthResultCallback)callback {
  1324. [FIRUser retrieveUserWithAuth:self
  1325. accessToken:accessToken
  1326. accessTokenExpirationDate:accessTokenExpirationDate
  1327. refreshToken:refreshToken
  1328. anonymous:anonymous
  1329. callback:callback];
  1330. }
  1331. /** @fn signInFlowAuthResultCallbackByDecoratingCallback:
  1332. @brief Creates a FIRAuthResultCallback block which wraps another FIRAuthResultCallback; trying
  1333. to update the current user before forwarding it's invocations along to a subject block
  1334. @param callback Called when the user has been updated or when an error has occurred. Invoked
  1335. asynchronously on the main thread in the future.
  1336. @return Returns a block that updates the current user.
  1337. @remarks Typically invoked as part of the complete sign-in flow. For any other uses please
  1338. consider alternative ways of updating the current user.
  1339. */
  1340. - (FIRAuthResultCallback)signInFlowAuthResultCallbackByDecoratingCallback:
  1341. (nullable FIRAuthResultCallback)callback {
  1342. return ^(FIRUser *_Nullable user, NSError *_Nullable error) {
  1343. if (error) {
  1344. if (callback) {
  1345. dispatch_async(dispatch_get_main_queue(), ^{
  1346. callback(nil, error);
  1347. });
  1348. }
  1349. return;
  1350. }
  1351. if (![self updateCurrentUser:user byForce:NO savingToDisk:YES error:&error]) {
  1352. if (callback) {
  1353. dispatch_async(dispatch_get_main_queue(), ^{
  1354. callback(nil, error);
  1355. });
  1356. }
  1357. return;
  1358. }
  1359. if (callback) {
  1360. dispatch_async(dispatch_get_main_queue(), ^{
  1361. callback(user, nil);
  1362. });
  1363. }
  1364. };
  1365. }
  1366. /** @fn signInFlowAuthDataResultCallbackByDecoratingCallback:
  1367. @brief Creates a FIRAuthDataResultCallback block which wraps another FIRAuthDataResultCallback;
  1368. trying to update the current user before forwarding it's invocations along to a subject
  1369. block.
  1370. @param callback Called when the user has been updated or when an error has occurred. Invoked
  1371. asynchronously on the main thread in the future.
  1372. @return Returns a block that updates the current user.
  1373. @remarks Typically invoked as part of the complete sign-in flow. For any other uses please
  1374. consider alternative ways of updating the current user.
  1375. */
  1376. - (FIRAuthDataResultCallback)signInFlowAuthDataResultCallbackByDecoratingCallback:
  1377. (nullable FIRAuthDataResultCallback)callback {
  1378. return ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
  1379. if (error) {
  1380. if (callback) {
  1381. dispatch_async(dispatch_get_main_queue(), ^{
  1382. callback(nil, error);
  1383. });
  1384. }
  1385. return;
  1386. }
  1387. if (![self updateCurrentUser:authResult.user byForce:NO savingToDisk:YES error:&error]) {
  1388. if (callback) {
  1389. dispatch_async(dispatch_get_main_queue(), ^{
  1390. callback(nil, error);
  1391. });
  1392. }
  1393. return;
  1394. }
  1395. if (callback) {
  1396. dispatch_async(dispatch_get_main_queue(), ^{
  1397. callback(authResult, nil);
  1398. });
  1399. }
  1400. };
  1401. }
  1402. #pragma mark - User-Related Methods
  1403. /** @fn updateCurrentUser:savingToDisk:
  1404. @brief Update the current user; initializing the user's internal properties correctly, and
  1405. optionally saving the user to disk.
  1406. @remarks This method is called during: sign in and sign out events, as well as during class
  1407. initialization time. The only time the saveToDisk parameter should be set to NO is during
  1408. class initialization time because the user was just read from disk.
  1409. @param user The user to use as the current user (including nil, which is passed at sign out
  1410. time.)
  1411. @param saveToDisk Indicates the method should persist the user data to disk.
  1412. */
  1413. - (BOOL)updateCurrentUser:(FIRUser *)user
  1414. byForce:(BOOL)force
  1415. savingToDisk:(BOOL)saveToDisk
  1416. error:(NSError *_Nullable *_Nullable)error {
  1417. if (user == _currentUser) {
  1418. [self possiblyPostAuthStateChangeNotification];
  1419. return YES;
  1420. }
  1421. BOOL success = YES;
  1422. if (saveToDisk) {
  1423. success = [self saveUser:user error:error];
  1424. }
  1425. if (success || force) {
  1426. _currentUser = user;
  1427. [self possiblyPostAuthStateChangeNotification];
  1428. }
  1429. return success;
  1430. }
  1431. /** @fn saveUser:error:
  1432. @brief Persists user.
  1433. @param user The user to save.
  1434. @param error Return value for any error which occurs.
  1435. @return @YES on success, @NO otherwise.
  1436. */
  1437. - (BOOL)saveUser:(FIRUser *)user
  1438. error:(NSError *_Nullable *_Nullable)error {
  1439. BOOL success;
  1440. NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName];
  1441. if (!user) {
  1442. success = [_keychain removeDataForKey:userKey error:error];
  1443. } else {
  1444. // Encode the user object.
  1445. NSMutableData *archiveData = [NSMutableData data];
  1446. NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData];
  1447. [archiver encodeObject:user forKey:userKey];
  1448. [archiver finishEncoding];
  1449. // Save the user object's encoded value.
  1450. success = [_keychain setData:archiveData forKey:userKey error:error];
  1451. }
  1452. return success;
  1453. }
  1454. /** @fn getUser:error:
  1455. @brief Retrieves the saved user associated, if one exists, from the keychain.
  1456. @param outUser An out parameter which is populated with the saved user, if one exists.
  1457. @param error Return value for any error which occurs.
  1458. @return YES if the operation was a success (irrespective of whether or not a saved user existed
  1459. for the given @c firebaseAppId,) NO if an error occurred.
  1460. */
  1461. - (BOOL)getUser:(FIRUser *_Nullable *)outUser
  1462. error:(NSError *_Nullable *_Nullable)error {
  1463. NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName];
  1464. NSError *keychainError;
  1465. NSData *encodedUserData = [_keychain dataForKey:userKey error:&keychainError];
  1466. if (keychainError) {
  1467. if (error) {
  1468. *error = keychainError;
  1469. }
  1470. return NO;
  1471. }
  1472. if (!encodedUserData) {
  1473. *outUser = nil;
  1474. return YES;
  1475. }
  1476. NSKeyedUnarchiver *unarchiver =
  1477. [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData];
  1478. FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey];
  1479. user.auth = self;
  1480. *outUser = user;
  1481. return YES;
  1482. }
  1483. - (nullable NSString *)getUID {
  1484. return _currentUser.uid;
  1485. }
  1486. @end