From 04efac46208c946956f17187e8755f0ea2520b16 Mon Sep 17 00:00:00 2001 From: Liubin Jiang Date: Mon, 2 Oct 2023 15:56:25 -0700 Subject: [PATCH] start passkey sign in request & response --- FirebaseAuth/Sources/Backend/FIRAuthBackend.h | 30 +++ FirebaseAuth/Sources/Backend/FIRAuthBackend.m | 22 ++ .../RPC/FIRStartPasskeySignInRequest.h | 32 +++ .../RPC/FIRStartPasskeySignInRequest.m | 57 +++++ .../RPC/FIRStartPasskeySignInResponse.h | 41 +++ .../RPC/FIRStartPasskeySignInResponse.m | 57 +++++ .../Unit/FIRStartPasskeySignInRequestTests.m | 96 +++++++ .../Unit/FIRStartPasskeySignInResponseTests.m | 239 ++++++++++++++++++ 8 files changed, 574 insertions(+) create mode 100644 FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h create mode 100644 FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.m create mode 100644 FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h create mode 100644 FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.m create mode 100644 FirebaseAuth/Tests/Unit/FIRStartPasskeySignInRequestTests.m create mode 100644 FirebaseAuth/Tests/Unit/FIRStartPasskeySignInResponseTests.m diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h index 52dca974389..fd0d2f7da14 100644 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h +++ b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h @@ -60,6 +60,8 @@ @class FIRGetRecaptchaConfigResponse; @class FIRStartPasskeyEnrollmentRequest; @class FIRStartPasskeyEnrollmentResponse; +@class FIRStartPasskeySignInRequest; +@class FIRStartPasskeySignInResponse; @protocol FIRAuthBackendImplementation; @protocol FIRAuthBackendRPCIssuer; @@ -266,6 +268,17 @@ endpoint. typedef void (^FIRStartPasskeyEnrollmentResponseCallback)( FIRStartPasskeyEnrollmentResponse *_Nullable response, NSError *_Nullable error); +/** + @typedef FIRStartPasskeySinInResponseCallback + @brief The type of block used to return the result of a call to the StartPasskeySignIn +endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRStartPasskeySignInResponseCallback)( + FIRStartPasskeySignInResponse *_Nullable response, NSError *_Nullable error); + /** @class FIRAuthBackend @brief Simple static class with methods representing the backend RPCs. @remarks All callback blocks passed as method parameters are invoked asynchronously on the @@ -471,6 +484,15 @@ typedef void (^FIRStartPasskeyEnrollmentResponseCallback)( */ + (void)startPasskeyEnrollment:(FIRStartPasskeyEnrollmentRequest *)request callback:(FIRStartPasskeyEnrollmentResponseCallback)callback; + +/** @fn startPasskeySignInt:callback: + @brief Calls the startPasskeySignIn endpoint, which is responsible for receving the + challenge that will later be consumed for platform key attestation. + @param request The request parameters. + @param callback The callback. + */ ++ (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request + callback:(FIRStartPasskeySignInResponseCallback)callback; #endif /** @fn revokeToken:callback: @@ -656,6 +678,14 @@ typedef void (^FIRStartPasskeyEnrollmentResponseCallback)( */ - (void)startPasskeyEnrollment:(FIRStartPasskeyEnrollmentRequest *)request callback:(FIRStartPasskeyEnrollmentResponseCallback)callback; + +/** @fn startPasskeySignIn:callback: + @brief Calls the startPasskeySignIn endpoint, which is responsible for receving the challange. + @param request The request parameters. + @param callback The callback. + */ +- (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request + callback:(FIRStartPasskeySignInResponseCallback)callback; #endif /** @fn revokeToken:callback: diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m index 53c98e00c0a..4770916e5f2 100644 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m +++ b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m @@ -60,6 +60,8 @@ #import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeyEnrollmentRequest.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeyEnrollmentResponse.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientRequest.h" @@ -676,6 +678,11 @@ + (void)verifyClient:(id)request callback:(FIRVerifyClientResponseCallback)callb #endif #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST ++ (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request + callback:(FIRStartPasskeySignInResponseCallback)callback { + [[self implementation] startPasskeySignIn:request callback:callback]; +} + + (void)startPasskeyEnrollment:(FIRStartPasskeyEnrollmentRequest *)request callback:(FIRStartPasskeyEnrollmentResponseCallback)callback { [[self implementation] startPasskeyEnrollment:request callback:callback]; @@ -1109,6 +1116,21 @@ - (void)verifyClient:(id)request callback:(FIRVerifyClientResponseCallback)callb #endif #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST + +- (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request + callback:(FIRStartPasskeySignInResponseCallback)callback { + FIRStartPasskeySignInResponse *response = [[FIRStartPasskeySignInResponse alloc] init]; + [self callWithRequest:request + response:response + callback:^(NSError *error) { + if (error) { + callback(nil, error); + return; + } + callback(response, nil); + }]; +} + - (void)startPasskeyEnrollment:(FIRStartPasskeyEnrollmentRequest *)request callback:(FIRStartPasskeyEnrollmentResponseCallback)callback { FIRStartPasskeyEnrollmentResponse *response = [[FIRStartPasskeyEnrollmentResponse alloc] init]; diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h new file mode 100644 index 00000000000..bdbfaa5647f --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAuth/Sources/Backend/FIRAuthRPCRequest.h" +#import "FirebaseAuth/Sources/Backend/FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRStartPasskeySignInRequest + @brief Represents the parameters for the startPasskeySignIn endpoint. + */ +@interface FIRStartPasskeySignInRequest : FIRIdentityToolkitRequest + +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.m b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.m new file mode 100644 index 00000000000..47fadf53f9e --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.m @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + @var kStartPasskeySignInEndPoint + @brief GCIP endpoint for startPasskeySignIn rpc + */ +static NSString *const kStartPasskeySignInEndPoint = @"accounts/passkeySignIn:start"; + +/** + @var kTenantIDKey + @brief The key for the tenant id value in the request. + */ +static NSString *const kTenantIDKey = @"tenantId"; + +@implementation FIRStartPasskeySignInRequest + +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kStartPasskeySignInEndPoint + requestConfiguration:requestConfiguration]; + + if (self) { + self.useIdentityPlatform = YES; + } + + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Nullable *)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (self.tenantID) { + postBody[kTenantIDKey] = self.tenantID; + } + return [postBody copy]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h new file mode 100644 index 00000000000..128562ce3c6 --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAuth/Sources/Backend/FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + @class FIRStartPasskeySignInResponse + @brief Represents the response from the startPasskeySignIn endpoint. + */ +@interface FIRStartPasskeySignInResponse : NSObject + +/** + @property rpID + @brief The RP ID of the FIDO Relying Party. + */ +@property(nonatomic, readonly, copy) NSString *rpID; + +/** + @property challenge + @brief The FIDO challenge. + */ +@property(nonatomic, readonly, copy) NSString *challenge; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.m b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.m new file mode 100644 index 00000000000..e40bd294516 --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.m @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h" + +/** + @var kOptionsKey + @brief Parameters specified for the authenticator to sign a challenge. + */ +static const NSString *kOptionsKey = @"credentialRequestOptions"; + +/** + @var kRpIdKey + @brief // The relying party identifier. + */ +static const NSString *kRpIdKey = @"rpId"; + +/** + @var kChallengeKey + @brief The name of the field in the response JSON for challenge. + */ +static const NSString *kChallengeKey = @"challenge"; + +@implementation FIRStartPasskeySignInResponse + +- (BOOL)setWithDictionary:(nonnull NSDictionary *)dictionary + error:(NSError *__autoreleasing _Nullable *_Nullable)error { + if (dictionary[kOptionsKey] == nil) { + return NO; + } + if (dictionary[kOptionsKey][kRpIdKey] == nil) { + return NO; + } + + if (dictionary[kOptionsKey][kChallengeKey] == nil) { + return NO; + } + + _rpID = dictionary[kOptionsKey][kRpIdKey]; + _challenge = dictionary[kOptionsKey][kChallengeKey]; + return YES; +} + +@end diff --git a/FirebaseAuth/Tests/Unit/FIRStartPasskeySignInRequestTests.m b/FirebaseAuth/Tests/Unit/FIRStartPasskeySignInRequestTests.m new file mode 100644 index 00000000000..7fbb9126e55 --- /dev/null +++ b/FirebaseAuth/Tests/Unit/FIRStartPasskeySignInRequestTests.m @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST + +#import + +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h" +#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthErrors.h" +#import "FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h" + +/** + @var kTestAPIKey + @brief Fake API key used for testing. + */ +static NSString *const kTestAPIKey = @"APIKey"; + +/** + @var kTestFirebaseAppID + @brief Fake Firebase app ID used for testing. + */ +static NSString *const kTestFirebaseAppID = @"appID"; + +/** + @var kExpectedAPIURL + @brief The expected URL for the test calls. + */ +static NSString *const kExpectedAPIURL = + @"https://identitytoolkit.googleapis.com/v2/accounts/passkeySignIn:start?key=APIKey"; + +/** + @class FIRStartPasskeySignInRequestTests + @brief Tests for @c FIRStartPasskeySignInRequest. + */ +@interface FIRStartPasskeySignInRequestTests : XCTestCase +@end + +@implementation FIRStartPasskeySignInRequestTests { + /** + @brief This backend RPC issuer is used to fake network responses for each test in the suite. + In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it. + */ + FIRFakeBackendRPCIssuer *_RPCIssuer; + + /** + @brief This is the request configuration used for testing. + */ + FIRAuthRequestConfiguration *_requestConfiguration; +} + +- (void)setUp { + [super setUp]; + FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init]; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer]; + _RPCIssuer = RPCIssuer; + _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey + appID:kTestFirebaseAppID]; +} + +- (void)tearDown { + _RPCIssuer = nil; + _requestConfiguration = nil; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; + [super tearDown]; +} + +- (void)testStartPasskeySignInRequest { + FIRStartPasskeySignInRequest *request = + [[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:_requestConfiguration]; + + [FIRAuthBackend startPasskeySignIn:request + callback:^(FIRStartPasskeySignInResponse *_Nullable response, + NSError *_Nullable error){ + }]; + + XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL); + XCTAssertNotNil(_RPCIssuer.decodedRequest); +} + +@end +#endif diff --git a/FirebaseAuth/Tests/Unit/FIRStartPasskeySignInResponseTests.m b/FirebaseAuth/Tests/Unit/FIRStartPasskeySignInResponseTests.m new file mode 100644 index 00000000000..d145a726906 --- /dev/null +++ b/FirebaseAuth/Tests/Unit/FIRStartPasskeySignInResponseTests.m @@ -0,0 +1,239 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST +#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthErrors.h" + +#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h" +#import "FirebaseAuth/Sources/Utilities/FIRAuthInternalErrors.h" +#import "FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h" + +/** + @var kTestAPIKey + @brief Fake API key used for testing. + */ +static NSString *const kTestAPIKey = @"APIKey"; + +/** + @var kTestFirebaseAppID + @brief Fake Firebase app ID used for testing. + */ +static NSString *const kTestFirebaseAppID = @"appID"; + +/** + @var kTestRpID + @brief Fake Relying Party ID used for testing. + */ +static NSString *const kTestRpID = @"1234567890"; + +/** + @var kTestChallenge + @brief Fake challenge used for testing. + */ +static NSString *const kTestChallenge = @"challengebytes"; + +/** + @var kTestRpKey + @brief the name of the "rp" property in the response. + */ +static NSString *const kRpKey = @"rpId"; + +/** + @var kTestChallengeKey + @brief the name of the "challenge" property in the response. + */ +static NSString *const kChallengeKey = @"challenge"; + +/** + @class FIRStartPasskeySignInResponseTests + @brief Tests for @c FIRStartPasskeySingInResponse. + */ +@interface FIRStartPasskeySignInResponseTests : XCTestCase +@end +@implementation FIRStartPasskeySignInResponseTests { + /** + @brief This backend RPC issuer is used to fake network responses for each test in the suite. + In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it. + */ + FIRFakeBackendRPCIssuer *_RPCIssuer; + + /** + @brief This is the request configuration used for testing. + */ + FIRAuthRequestConfiguration *_requestConfiguration; +} + +- (void)setUp { + [super setUp]; + FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init]; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer]; + _RPCIssuer = RPCIssuer; + _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey + appID:kTestFirebaseAppID]; +} + +- (void)tearDown { + _RPCIssuer = nil; + _requestConfiguration = nil; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; + [super tearDown]; +} + +/** @fn testSuccessfulStartPasskeySignInResponse + @brief This test simulates a successful @c StartPasskeySignIn flow. + */ +- (void)testSuccessfulStartPasskeySignInResponse { + FIRStartPasskeySignInRequest *request = + [[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRStartPasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend startPasskeySignIn:request + callback:^(FIRStartPasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"credentialRequestOptions" : @{ + kChallengeKey : kTestChallenge, + kRpKey : kTestRpID, + }, + }]; + + XCTAssert(callbackInvoked); + XCTAssertNil(RPCError); + XCTAssertNotNil(RPCResponse); + XCTAssertEqualObjects(RPCResponse.rpID, kTestRpID); + XCTAssertEqualObjects(RPCResponse.challenge, kTestChallenge); +} + +/** @fn testStartPasskeySignInResponseMissingRequestOptionsError + @brief This test simulates an unexpected response returned from server in @c + StartPasskeySignIn flow. + */ +- (void)testStartPasskeySignInResponseMissingRequestOptionsError { + FIRStartPasskeySignInRequest *request = + [[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRStartPasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend startPasskeySignIn:request + callback:^(FIRStartPasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"wrongkey" : @{}, + }]; + [self errorValidationHelperWithCallbackInvoked:callbackInvoked + rpcError:RPCError + rpcResponse:RPCResponse]; +} + +/** @fn testStartPasskeySignInResponseMissingRpIdError + @brief This test simulates an unexpected response returned from server in @c + StartPasskeySignIn flow. + */ +- (void)testStartPasskeySignInResponseMissingRpIdError { + FIRStartPasskeySignInRequest *request = + [[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRStartPasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend startPasskeySignIn:request + callback:^(FIRStartPasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"credentialRequestOptions" : @{ + kChallengeKey : kTestChallenge, + }, + }]; + [self errorValidationHelperWithCallbackInvoked:callbackInvoked + rpcError:RPCError + rpcResponse:RPCResponse]; +} + +/** @fn testStartPasskeySignInResponseMissingChallengeError + @brief This test simulates an unexpected response returned from server in @c + StartPasskeySignIn flow. + */ +- (void)testStartPasskeySignInResponseMissingChallengeError { + FIRStartPasskeySignInRequest *request = + [[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRStartPasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend startPasskeySignIn:request + callback:^(FIRStartPasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"credentialCreationOptions" : @{ + kRpKey : kTestRpID, + }, + }]; + [self errorValidationHelperWithCallbackInvoked:callbackInvoked + rpcError:RPCError + rpcResponse:RPCResponse]; +} + +/** @fn errorValidationHelperWithCallbackInvoked:rpcError:rpcResponse: + @brief Helper function to validate the unexpected response returned from server in @c + StartPasskeySignIn flow. + */ +- (void)errorValidationHelperWithCallbackInvoked:(BOOL)callbackInvoked + rpcError:(NSError *)RPCError + rpcResponse:(FIRStartPasskeySignInResponse *)RPCResponse { + XCTAssert(callbackInvoked); + XCTAssertNotNil(RPCError); + XCTAssertEqualObjects(RPCError.domain, FIRAuthErrorDomain); + XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInternalError); + XCTAssertNotNil(RPCError.userInfo[NSUnderlyingErrorKey]); + NSError *underlyingError = RPCError.userInfo[NSUnderlyingErrorKey]; + XCTAssertNotNil(underlyingError); + XCTAssertNotNil(underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]); + XCTAssertNil(RPCResponse); +} + +@end +#endif