Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Example/Auth/Sample/MainViewController+Email.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@

NS_ASSUME_NONNULL_BEGIN

typedef void (^ShowEmailPasswordDialogCompletion)(FIRAuthCredential *credential);

@interface MainViewController (Email)

- (StaticContentTableViewSection *)emailAuthSection;
Expand Down
80 changes: 60 additions & 20 deletions Example/Auth/Sample/MainViewController+Email.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

NS_ASSUME_NONNULL_BEGIN

typedef void (^ShowEmailDialogCompletion)(FIRAuthCredential *credential);

@implementation MainViewController (Email)

- (StaticContentTableViewSection *)emailAuthSection {
Expand All @@ -36,10 +38,12 @@ - (StaticContentTableViewSection *)emailAuthSection {
action:^{ [weakSelf unlinkFromProvider:FIREmailAuthProviderID completion:nil]; }],
[StaticContentTableViewCell cellWithTitle:@"Reauthenticate Email Password"
action:^{ [weakSelf reauthenticateEmailPassword]; }],
[StaticContentTableViewCell cellWithTitle:@"Sign in with Email Link"
action:^{ [weakSelf sendEmailSignInLink]; }],
[StaticContentTableViewCell cellWithTitle:@"Send Email Sign in Link"
action:^{ [weakSelf sendEmailSignInLink]; }],
[StaticContentTableViewCell cellWithTitle:@"Sign in with Email Link"
action:^{ [weakSelf signInWithEmailLink]; }],
[StaticContentTableViewCell cellWithTitle:@"Link with Email Link"
action:^{ [weakSelf linkWithEmailLink]; }],
]];
}

Expand Down Expand Up @@ -173,24 +177,6 @@ - (void)reauthenticateEmailPassword {
}];
}

- (void)showEmailPasswordDialogWithCompletion:(ShowEmailPasswordDialogCompletion)completion {
[self showTextInputPromptWithMessage:@"Email Address:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
if (!userPressedOK || !email.length) {
return;
}
[self showTextInputPromptWithMessage:@"Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
if (!userPressedOK || !password.length) {
return;
}
FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email
password:password];
completion(credential);
}];
}];
}

- (void)signInWithEmailLink {
[self showTextInputPromptWithMessage:@"Email Address:"
keyboardType:UIKeyboardTypeEmailAddress
Expand Down Expand Up @@ -254,6 +240,60 @@ - (void)sendEmailSignInLink {
}];
}

- (void)linkWithEmailLink {
[self showEmailLinkDialogWithCompletion:^(FIRAuthCredential *credential) {
[self showSpinner:^{
[[self user] linkWithCredential:credential
completion:^(FIRAuthDataResult *result, NSError *error) {
if (error) {
[self logFailure:@"link Email Link failed." error:error];
} else {
[self logSuccess:@"link Email Link succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:@"Link with Email Link" error:error];
}];
}];
}];
}];
}

- (void)showEmailPasswordDialogWithCompletion:(ShowEmailDialogCompletion)completion {
[self showTextInputPromptWithMessage:@"Email Address:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
if (!userPressedOK || !email.length) {
return;
}
[self showTextInputPromptWithMessage:@"Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
if (!userPressedOK || !password.length) {
return;
}
FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email
password:password];
completion(credential);
}];
}];
}

- (void)showEmailLinkDialogWithCompletion:(ShowEmailDialogCompletion)completion {
[self showTextInputPromptWithMessage:@"Email Address:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
if (!userPressedOK || !email.length) {
return;
}
[self showTextInputPromptWithMessage:@"Link:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable link) {
if (!userPressedOK || !link.length) {
return;
}
FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email
link:link];
completion(credential);
}];
}];
}

@end

NS_ASSUME_NONNULL_END
58 changes: 0 additions & 58 deletions Example/Auth/Tests/FIRUserTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1627,64 +1627,6 @@ - (void)testlinkAndRetrieveDataErrorAutoSignOut {
OCMVerifyAll(_mockBackend);
}

/** @fn testLinkingAnonymousAccountsUpdatesIsAnonymous
@brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
invocation for email credential.
*/
- (void)testLinkingAnonymousAccountsUpdatesIsAnonymous {
FIRAuthCredential *linkEmailCredential =
[FIREmailAuthProvider credentialWithEmail:kEmail
link:@"https://google.com?oobCode=aCode&mode=signIn"];

id (^mockUserInfoWithDisplayName)(NSString *, BOOL) = ^(NSString *displayName,
BOOL hasProviders) {
NSArray *providers = hasProviders ? @[ @{
@"providerId": FIREmailAuthProviderID,
@"email": kEmail
} ] : @[];
FIRGetAccountInfoResponseUser *responseUser =
[[FIRGetAccountInfoResponseUser alloc] initWithDictionary:@{
@"providerUserInfo": providers,
@"localId": kLocalID,
@"displayName": displayName,
@"email": kEmail
}];
return responseUser;
};
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName, NO);

[self signInAnonymouslyWithMockGetAccountInfoResponse:userInfoResponse
completion:^(FIRUser *user) {
// Pretend that the display name and providers on the server have been updated.
// Get account info is expected to be invoked twice.
id updatedMockUser = mockUserInfoWithDisplayName(kNewDisplayName, YES);
[self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser];
[self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser];
OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
FIRSetAccountInfoResponseCallback callback) {
id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail);
OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
callback(mockSetAccountInfoResponse, nil);
});
XCTAssertTrue(user.isAnonymous);

[user linkWithCredential:linkEmailCredential
completion:^(FIRAuthDataResult *_Nullable linkAuthResult,
NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
XCTAssertNil(error);
XCTAssertEqualObjects(user.email, kEmail);
XCTAssertFalse(user.isAnonymous);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}

/** @fn testlinkEmailAndRetrieveDataSuccess
@brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
invocation for email credential.
Expand Down
37 changes: 5 additions & 32 deletions Firebase/Auth/Source/Auth/FIRAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#import "FIRAuthOperationType.h"
#import "FIRAuthSettings.h"
#import "FIRAuthStoredUserManager.h"
#import "FIRAuthWebUtils.h"
#import "FIRUser_Internal.h"
#import "FirebaseAuth.h"
#import "FIRAuthBackend.h"
Expand Down Expand Up @@ -678,10 +679,10 @@ - (void)internalSignInAndRetrieveDataWithEmail:(nonnull NSString *)email
kInvalidEmailSignInLinkExceptionMessage];
return;
}
NSDictionary<NSString *, NSString *> *queryItems = FIRAuthParseURL(link);
NSDictionary<NSString *, NSString *> *queryItems = [FIRAuthWebUtils parseURL:link];
if (![queryItems count]) {
NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link];
queryItems = FIRAuthParseURL(urlComponents.query);
queryItems = [FIRAuthWebUtils parseURL:urlComponents.query];
}
NSString *actionCode = queryItems[@"oobCode"];

Expand Down Expand Up @@ -1198,13 +1199,13 @@ - (BOOL)isSignInWithEmailLink:(NSString *)link {
if (link.length == 0) {
return NO;
}
NSDictionary<NSString *, NSString *> *queryItems = FIRAuthParseURL(link);
NSDictionary<NSString *, NSString *> *queryItems = [FIRAuthWebUtils parseURL:link];
if (![queryItems count]) {
NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link];
if (!urlComponents.query) {
return NO;
}
queryItems = FIRAuthParseURL(urlComponents.query);
queryItems = [FIRAuthWebUtils parseURL:urlComponents.query];
}

if (![queryItems count]) {
Expand All @@ -1220,34 +1221,6 @@ - (BOOL)isSignInWithEmailLink:(NSString *)link {
return NO;
}

/** @fn FIRAuthParseURL:NSString
@brief Parses an incoming URL into all available query items.
@param urlString The url to be parsed.
@return A dictionary of available query items in the target URL.
*/
static NSDictionary<NSString *, NSString *> *FIRAuthParseURL(NSString *urlString) {
NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
if (!linkURL) {
return @{};
}
NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
NSMutableDictionary<NSString *, NSString *> *queryItems =
[[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
for (NSString *component in URLComponents) {
NSRange equalRange = [component rangeOfString:@"="];
if (equalRange.location != NSNotFound) {
NSString *queryItemKey =
[[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
NSString *queryItemValue =
[[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
if (queryItemKey && queryItemValue) {
queryItems[queryItemKey] = queryItemValue;
}
}
}
return queryItems;
}

- (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener:
(FIRAuthStateDidChangeListenerBlock)listener {
__block BOOL firstInvocation = YES;
Expand Down
73 changes: 64 additions & 9 deletions Firebase/Auth/Source/User/FIRUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
#import "FIRAuthBackend.h"
#import "FIRAuthRequestConfiguration.h"
#import "FIRAuthTokenResult_Internal.h"
#import "FIRAuthWebUtils.h"
#import "FIRDeleteAccountRequest.h"
#import "FIRDeleteAccountResponse.h"
#import "FIREmailAuthProvider.h"
#import "FIREmailPasswordAuthCredential.h"
#import "FIREmailLinkSignInRequest.h"
#import "FIRGameCenterAuthCredential.h"
#import "FIRGetAccountInfoRequest.h"
#import "FIRGetAccountInfoResponse.h"
Expand Down Expand Up @@ -1026,15 +1028,68 @@ - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
}
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);
}
}];
if (emailPasswordCredential.password) {
[self updateEmail:emailPasswordCredential.email
password:emailPasswordCredential.password
callback:^(NSError *error) {
if (error) {
callInMainThreadWithAuthDataResultAndError(completion, nil, error);
} else {
callInMainThreadWithAuthDataResultAndError(completion, result, nil);
}
}];
} else {
[self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
NSError *_Nullable error) {
NSDictionary<NSString *, NSString *> *queryItems = [FIRAuthWebUtils parseURL:emailPasswordCredential.link];
if (![queryItems count]) {
NSURLComponents *urlComponents = [NSURLComponents componentsWithString:emailPasswordCredential.link];
queryItems = [FIRAuthWebUtils parseURL:urlComponents.query];
}
NSString *actionCode = queryItems[@"oobCode"];
FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration;
FIREmailLinkSignInRequest *request =
[[FIREmailLinkSignInRequest alloc] initWithEmail:emailPasswordCredential.email
oobCode:actionCode
requestConfiguration:requestConfiguration];
request.IDToken = accessToken;
[FIRAuthBackend emailLinkSignin:request
callback:^(FIREmailLinkSignInResponse *_Nullable response,
NSError *_Nullable error) {
if (error){
callInMainThreadWithAuthDataResultAndError(completion, nil, error);
} else {
[self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
NSError *_Nullable error) {
if (error) {
callInMainThreadWithAuthDataResultAndError(completion, nil, error);
return;
}

FIRGetAccountInfoRequest *getAccountInfoRequest =
[[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
requestConfiguration:requestConfiguration];
[FIRAuthBackend getAccountInfo:getAccountInfoRequest
callback:^(FIRGetAccountInfoResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
[self signOutIfTokenIsInvalidWithError:error];
callInMainThreadWithAuthDataResultAndError(completion, nil, error);
return;
}
self.anonymous = NO;
[self updateWithGetAccountInfoResponse:response];
if (![self updateKeychain:&error]) {
callInMainThreadWithAuthDataResultAndError(completion, nil, error);
return;
}
callInMainThreadWithAuthDataResultAndError(completion, result, nil);
}];
}];
}
}];
}];
}
return;
}

Expand Down
7 changes: 7 additions & 0 deletions Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain,
*/
+ (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument;

/** @fn parseURL:
@brief Parses an incoming URL into all available query items.
@param urlString The url to be parsed.
@return A dictionary of available query items in the target URL.
*/
+ (NSDictionary<NSString *, NSString *> *)parseURL:(NSString *)urlString;

@end

NS_ASSUME_NONNULL_END
23 changes: 23 additions & 0 deletions Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,29 @@ + (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument {
return [resultString stringByRemovingPercentEncoding];
}

+ (NSDictionary<NSString *, NSString *> *)parseURL:(NSString *)urlString {
NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
if (!linkURL) {
return @{};
}
NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
NSMutableDictionary<NSString *, NSString *> *queryItems =
[[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
for (NSString *component in URLComponents) {
NSRange equalRange = [component rangeOfString:@"="];
if (equalRange.location != NSNotFound) {
NSString *queryItemKey =
[[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
NSString *queryItemValue =
[[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
if (queryItemKey && queryItemValue) {
queryItems[queryItemKey] = queryItemValue;
}
}
}
return queryItems;
}

@end

NS_ASSUME_NONNULL_END