diff --git a/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m b/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m index b50e866d314..cf265462e85 100644 --- a/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m +++ b/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m @@ -747,23 +747,7 @@ - (void)testCaptchaCheckFailedResponse { XCTAssertNotNil(callbackError); XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNil(underlyingUnderlyingError); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]); - XCTAssertNotNil(deserializedResponse[@"message"]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); + XCTAssertEqual(callbackError.code, FIRAuthErrorCodeCaptchaCheckFailed); } /** @fn testCaptchaRequiredInvalidPasswordResponse diff --git a/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m b/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m index 0f0b420399e..c908aadc17f 100644 --- a/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m +++ b/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m @@ -42,6 +42,11 @@ */ static NSString *const kTestReceipt = @"receipt"; +/** @var kTestReCAPTCHAToken + @brief Fake reCAPTCHA token used for testing. + */ +static NSString *const kTestReCAPTCHAToken = @"reCAPTCHAToken"; + /** @var kPhoneNumberKey @brief The key for the "phone number" value in the request. */ @@ -101,10 +106,12 @@ - (void)testSendVerificationCodeRequest { FIRSendVerificationCodeRequest *request = [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber appCredential:credential + reCAPTCHAToken:kTestReCAPTCHAToken requestConfiguration:requestConfiguration]; XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber); XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt); XCTAssertEqualObjects(request.appCredential.secret, kTestSecret); + XCTAssertEqualObjects(request.reCAPTCHAToken, kTestReCAPTCHAToken); [FIRAuthBackend sendVerificationCode:request callback:^(FIRSendVerificationCodeResponse *_Nullable response, diff --git a/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m b/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m index 6f917ce4523..87620076119 100644 --- a/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m +++ b/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m @@ -59,6 +59,11 @@ */ static NSString *const kTestReceipt = @"receipt"; +/** @var kTestReCAPTCHAToken + @brief Fake reCAPTCHA token used for testing. + */ +static NSString *const kTestReCAPTCHAToken = @"reCAPTCHAToken"; + /** @var kInvalidPhoneNumberErrorMessage @brief This is the error message the server will respond with if an incorrectly formatted phone number is provided. @@ -77,6 +82,12 @@ */ static NSString *const kAppNotVerifiedErrorMessage = @"APP_NOT_VERIFIED"; +/** @var kCaptchaCheckFailedErrorMessage + @brief This is the error message the server will respond with if the reCAPTCHA token provided is + invalid. + */ +static NSString *const kCaptchaCheckFailedErrorMessage = @"CAPTCHA_CHECK_FAILED"; + /** @class FIRSendVerificationCodeResponseTests @brief Tests for @c FIRSendVerificationCodeResponseTests. */ @@ -120,6 +131,7 @@ - (void)testSendVerificationCodeResponseInvalidPhoneNumber { FIRSendVerificationCodeRequest *request = [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestInvalidPhoneNumber appCredential:credential + reCAPTCHAToken:nil requestConfiguration:_requestConfiguration]; __block BOOL callbackInvoked; __block FIRSendVerificationCodeResponse *RPCResponse; @@ -148,6 +160,7 @@ - (void)testSendVerificationCodeResponseQuotaExceededError { FIRSendVerificationCodeRequest *request = [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber appCredential:credential + reCAPTCHAToken:nil requestConfiguration:_requestConfiguration]; __block BOOL callbackInvoked; __block FIRSendVerificationCodeResponse *RPCResponse; @@ -177,6 +190,7 @@ - (void)testSendVerificationCodeResponseAppNotVerifiedError { FIRSendVerificationCodeRequest *request = [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber appCredential:credential + reCAPTCHAToken:nil requestConfiguration:_requestConfiguration]; __block BOOL callbackInvoked; __block FIRSendVerificationCodeResponse *RPCResponse; @@ -196,6 +210,36 @@ - (void)testSendVerificationCodeResponseAppNotVerifiedError { XCTAssertEqual(RPCError.code, FIRAuthErrorCodeAppNotVerified); } +/** @fn testSendVerificationCodeResponseCaptchaCheckFailedError + @brief Tests a failed attempt to send a verification code due to an invalid reCAPTCHA token + being provided in the request. + */ +- (void)testSendVerificationCodeResponseCaptchaCheckFailedError { + FIRAuthAppCredential *credential = + [[FIRAuthAppCredential alloc]initWithReceipt:kTestReceipt secret:kTestSecret]; + FIRSendVerificationCodeRequest *request = + [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber + appCredential:credential + reCAPTCHAToken:kTestReCAPTCHAToken + requestConfiguration:_requestConfiguration]; + __block BOOL callbackInvoked; + __block FIRSendVerificationCodeResponse *RPCResponse; + __block NSError *RPCError; + [FIRAuthBackend sendVerificationCode:request + callback:^(FIRSendVerificationCodeResponse *_Nullable response, + NSError *_Nullable error) { + RPCResponse = response; + RPCError = error; + callbackInvoked = YES; + }]; + + [_RPCIssuer respondWithServerErrorMessage:kCaptchaCheckFailedErrorMessage]; + + XCTAssert(callbackInvoked); + XCTAssertNil(RPCResponse); + XCTAssertEqual(RPCError.code, FIRAuthErrorCodeCaptchaCheckFailed); +} + /** @fn testSuccessfulSendVerificationCodeResponse @brief Tests a succesful to send a verification code. */ @@ -205,6 +249,7 @@ - (void)testSuccessfulSendVerificationCodeResponse { FIRSendVerificationCodeRequest *request = [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber appCredential:credential + reCAPTCHAToken:nil requestConfiguration:_requestConfiguration]; __block BOOL callbackInvoked; __block FIRSendVerificationCodeResponse *RPCResponse; diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m index b456a2d167f..7c29f130023 100644 --- a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m +++ b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m @@ -132,6 +132,7 @@ - (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber FIRSendVerificationCodeRequest *request = [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber appCredential:appCredential + reCAPTCHAToken:nil requestConfiguration:_auth.requestConfiguration]; [FIRAuthBackend sendVerificationCode:request callback:^(FIRSendVerificationCodeResponse *_Nullable response, diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/FIRAuthErrorUtils.h index a857d4b2e1d..c2c91710ac3 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.h +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.h @@ -444,6 +444,13 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSError *)appNotVerifiedErrorWithMessage:(nullable NSString *)message; +/** @fn captchaCheckFailedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCaptchaCheckFailed code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message; + /** @fn keychainErrorWithFunction:status: @brief Constructs an @c NSError with the @c FIRAuthErrorCodeKeychainError code. @param keychainFunction The keychain function which was invoked and yielded an unexpected diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m index 42fb543955a..0439f81697b 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.m +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m @@ -339,6 +339,12 @@ "silent push notification and therefore could not verify your app. Ensure that you configured " "your app correctly to recieve push notifications."; +/** @var kFIRAuthErrorMessageCaptchaCheckFailed + @brief Message for @c FIRAuthErrorCodeCaptchaCheckFailed error code. + */ +static NSString *const kFIRAuthErrorMessageCaptchaCheckFailed = @"The reCAPTCHA response token " + "provided is either invalid, expired or already"; + /** @var kFIRAuthErrorMessageInternalError @brief Message for @c FIRAuthErrorCodeInternalError error code. */ @@ -447,6 +453,8 @@ return kFIRAuthErrorMessageNotificationNotForwarded; case FIRAuthErrorCodeAppNotVerified: return kFIRAuthErrorMessageAppNotVerified; + case FIRAuthErrorCodeCaptchaCheckFailed: + return kFIRAuthErrorMessageCaptchaCheckFailed; } } @@ -552,6 +560,8 @@ return @"ERROR_NOTIFICATION_NOT_FORWARDED"; case FIRAuthErrorCodeAppNotVerified: return @"ERROR_APP_NOT_VERIFIED"; + case FIRAuthErrorCodeCaptchaCheckFailed: + return @"ERROR_CAPTCHA_CHECK_FAILED"; } } @@ -859,6 +869,10 @@ + (NSError *)appNotVerifiedErrorWithMessage:(nullable NSString *)message { return [self errorWithCode:FIRAuthInternalErrorCodeAppNotVerified message:message]; } ++ (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeCaptchaCheckFailed message:message]; +} + + (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; return [self errorWithCode:FIRAuthInternalErrorCodeKeychainError userInfo:@{ diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/FIRAuthInternalErrors.h index 5f4a5c7e8d9..724b95c23c3 100644 --- a/Firebase/Auth/Source/FIRAuthInternalErrors.h +++ b/Firebase/Auth/Source/FIRAuthInternalErrors.h @@ -307,6 +307,11 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeInvalidAppCredential = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidAppCredential, + /** Indicates that the reCAPTCHA token is not valid. + */ + FIRAuthInternalErrorCodeCaptchaCheckFailed = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeCaptchaCheckFailed, + /** Indicates that an invalid verification ID was used in the verifyPhoneNumber request. */ FIRAuthInternalErrorCodeInvalidVerificationID = diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h index 2859161bc4f..6ab69008629 100644 --- a/Firebase/Auth/Source/Public/FIRAuthErrors.h +++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -272,6 +272,10 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { */ FIRAuthErrorCodeAppNotVerified = 17055, + /** Indicates that the reCAPTCHA token is not valid. + */ + FIRAuthErrorCodeCaptchaCheckFailed = 17056, + /** Indicates an error occurred while attempting to access the keychain. */ FIRAuthErrorCodeKeychainError = 17995, diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m index 8eca6d5f3dc..0964d3f9fad 100644 --- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m +++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m @@ -341,6 +341,12 @@ */ static NSString *const kAppNotVerifiedErrorMessage = @"APP_NOT_VERIFIED"; +/** @var kCaptchaCheckFailedErrorMessage + @brief This is the error message the server will respond with if the reCAPTCHA token provided is + invalid. + */ +static NSString *const kCaptchaCheckFailedErrorMessage = @"CAPTCHA_CHECK_FAILED"; + /** @var gBackendImplementation @brief The singleton FIRAuthBackendImplementation instance to use. */ @@ -1014,6 +1020,10 @@ + (nullable NSError *)clientErrorWithServerErrorMessage:(NSString *)serverErrorM return [FIRAuthErrorUtils appNotVerifiedErrorWithMessage:serverErrorMessage]; } + if ([shortErrorMessage isEqualToString:kCaptchaCheckFailedErrorMessage]) { + return [FIRAuthErrorUtils captchaCheckFailedErrorWithMessage:serverErrorMessage]; + } + // In this case we handle an error that might be specified in the underlying errors dictionary, // the error message in determined based on the @c reason key in the dictionary. if (errorDictionary[kErrorsKey]) { diff --git a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.h b/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.h index 9a5c41c431a..af6cc939f2f 100644 --- a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.h +++ b/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.h @@ -33,7 +33,13 @@ NS_ASSUME_NONNULL_BEGIN /** @property appCredential @brief The credential to prove the identity of the app in order to send the verification code. */ -@property(nonatomic, strong, readonly) FIRAuthAppCredential *appCredential; +@property(nonatomic, strong, readonly, nullable) FIRAuthAppCredential *appCredential; + +/** @property reCAPTCHAToken + @brief The reCAPTCHA token to prove the identity of the app in order to send the verification + code. + */ +@property(nonatomic, strong, readonly, nullable) NSString *reCAPTCHAToken; /** @fn initWithEndpoint:requestConfiguration: @brief Please use initWithPhoneNumber:appCredentials:requestConfiguration: instead. @@ -46,10 +52,12 @@ NS_ASSUME_NONNULL_BEGIN @brief Designated initializer. @param phoneNumber The phone number to which the verification code is to be sent. @param appCredential The credential that proves the identity of the app. + @param reCAPTCHAToken The reCAPTCHA token that proves the identity of the app. @param requestConfiguration An object containing configurations to be added to the request. */ - (nullable instancetype)initWithPhoneNumber:(NSString *)phoneNumber - appCredential:(FIRAuthAppCredential *)appCredential + appCredential:(nullable FIRAuthAppCredential *)appCredential + reCAPTCHAToken:(nullable NSString *)reCAPTCHAToken requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration NS_DESIGNATED_INITIALIZER; diff --git a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.m b/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.m index 2f33e02b09b..38ad8cff7d5 100644 --- a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.m +++ b/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.m @@ -40,17 +40,24 @@ */ static NSString *const kSecretKey = @"iosSecret"; +/** @var kreCAPTCHATokenKey + @brief The key for the reCAPTCHAToken parameter in the request. + */ +static NSString *const kreCAPTCHATokenKey = @"recaptchaToken"; + @implementation FIRSendVerificationCodeRequest { } - (nullable instancetype)initWithPhoneNumber:(NSString *)phoneNumber - appCredential:(FIRAuthAppCredential *)appCredential + appCredential:(nullable FIRAuthAppCredential *)appCredential + reCAPTCHAToken:(nullable NSString *)reCAPTCHAToken requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { self = [super initWithEndpoint:kSendVerificationCodeEndPoint requestConfiguration:requestConfiguration]; if (self) { _phoneNumber = [phoneNumber copy]; _appCredential = appCredential; + _reCAPTCHAToken = [reCAPTCHAToken copy]; } return self; } @@ -66,6 +73,9 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable) if (_appCredential.secret) { postBody[kSecretKey] = _appCredential.secret; } + if (_reCAPTCHAToken) { + postBody[kreCAPTCHATokenKey] = _reCAPTCHAToken; + } return postBody; }