diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h index 1a928888f38..a105bc3b5ee 100644 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h +++ b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h @@ -64,6 +64,8 @@ @class FIRFinalizePasskeyEnrollmentResponse; @class FIRStartPasskeySignInRequest; @class FIRStartPasskeySignInResponse; +@class FIRFinalizePasskeySignInRequest; +@class FIRFinalizePasskeySignInResponse; @protocol FIRAuthBackendImplementation; @protocol FIRAuthBackendRPCIssuer; @@ -272,7 +274,7 @@ typedef void (^FIRStartPasskeyEnrollmentResponseCallback)( /** @typedef FIRFinalizePasskeyEnrollmentResponseCallback - @brief The type of block used to return the result of a call to the startPasskeyEnrollment + @brief The type of block used to return the result of a call to the finalizePasskeyEnrollment endpoint. @param response The received response, if any. @param error The error which occurred, if any. @@ -292,6 +294,17 @@ endpoint. typedef void (^FIRStartPasskeySignInResponseCallback)( FIRStartPasskeySignInResponse *_Nullable response, NSError *_Nullable error); +/** + @typedef FIRFinalizePasskeySignInResponseCallback + @brief The type of block used to return the result of a call to the finalizePasskeySignIn + 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 (^FIRFinalizePasskeySignInResponseCallback)( + FIRFinalizePasskeySignInResponse *_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 @@ -514,6 +527,14 @@ typedef void (^FIRStartPasskeySignInResponseCallback)( */ + (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request callback:(FIRStartPasskeySignInResponseCallback)callback; + +/** @fn finalizePasskeySignIn:callback: + @brief Sends the platform created public info to the finalizePasskeySignIn endpoint. + @param request The request parameters. + @param callback The callback. + */ ++ (void)finalizePasskeySignIn:(FIRFinalizePasskeySignInRequest *)request + callback:(FIRFinalizePasskeySignInResponseCallback)callback; #endif /** @fn revokeToken:callback: @@ -700,7 +721,6 @@ typedef void (^FIRStartPasskeySignInResponseCallback)( - (void)startPasskeyEnrollment:(FIRStartPasskeyEnrollmentRequest *)request callback:(FIRStartPasskeyEnrollmentResponseCallback)callback; - /** @fn finalizePasskeyEnrollment:callback: @brief Calls the finalizePasskeyEnrollment endpoint, which is responsible for sending the platform credential details to GCIP backend to exchange the access token and refresh token. @@ -717,6 +737,14 @@ typedef void (^FIRStartPasskeySignInResponseCallback)( */ - (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request callback:(FIRStartPasskeySignInResponseCallback)callback; + +/** @fn finalizePasskeySignIn:callback: + @brief Sends the platform created public info to the finalizePasskeySignIn endpoint. + @param request The request parameters. + @param callback The callback. + */ +- (void)finalizePasskeySignIn:(FIRFinalizePasskeySignInRequest *)request + callback:(FIRFinalizePasskeySignInResponseCallback)callback; #endif /** @fn revokeToken:callback: diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m index 3ac9f8f2115..d4a55549c58 100644 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m +++ b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m @@ -38,6 +38,8 @@ #import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInResponse.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeyEnrollmentRequest.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeyEnrollmentResponse.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoRequest.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoResponse.h" #import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h" @@ -685,6 +687,11 @@ + (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request [[self implementation] startPasskeySignIn:request callback:callback]; } ++ (void)finalizePasskeySignIn:(FIRFinalizePasskeySignInRequest *)request + callback:(FIRFinalizePasskeySignInResponseCallback)callback { + [[self implementation] finalizePasskeySignIn:request callback:callback]; +} + + (void)startPasskeyEnrollment:(FIRStartPasskeyEnrollmentRequest *)request callback:(FIRStartPasskeyEnrollmentResponseCallback)callback { [[self implementation] startPasskeyEnrollment:request callback:callback]; @@ -1138,6 +1145,20 @@ - (void)startPasskeySignIn:(FIRStartPasskeySignInRequest *)request }]; } +- (void)finalizePasskeySignIn:(FIRFinalizePasskeySignInRequest *)request + callback:(FIRFinalizePasskeySignInResponseCallback)callback { + FIRFinalizePasskeySignInResponse *response = [[FIRFinalizePasskeySignInResponse 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/FIRFinalizePasskeyEnrollmentResponse.h b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeyEnrollmentResponse.h index 21c553bf9a8..826ba402ad6 100644 --- a/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeyEnrollmentResponse.h +++ b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeyEnrollmentResponse.h @@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN @property refershToken @brief Refresh token for the authenticated user. */ -@property(nonatomic, copy, readonly) NSData *refreshToken; +@property(nonatomic, copy, readonly) NSString *refreshToken; @end diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h new file mode 100644 index 00000000000..778d3328970 --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h @@ -0,0 +1,66 @@ +/* + * 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 FIRFinalizePasskeySignInRequest + @brief Represents the parameters for the finalizePasskeySignIn endpoint. + */ +@interface FIRFinalizePasskeySignInRequest : FIRIdentityToolkitRequest + +/** + @property credentialID + @brief The credential ID. + */ +@property(nonatomic, copy, readonly) NSString *credentialID; + +/** + @property clientDataJson + @brief The CollectedClientData object from the authenticator. + */ +@property(nonatomic, copy, readonly) NSString *clientDataJson; + +/** + @property authenticatorData + @brief The AuthenticatorData from the authenticator. + */ +@property(nonatomic, copy, readonly) NSString *authenticatorData; + +/** + @property signature + @brief The signature from the authenticator. + */ +@property(nonatomic, copy, readonly) NSString *signature; + +/** + @property userID + @brief The user handle + */ +@property(nonatomic, copy, readonly) NSString *userID; + +- (nullable instancetype)initWithCredentialID:(NSString *)credentialID + clientDataJson:(NSString *)clientDataJson + authenticatorData:(NSString *)authenticatorData + signature:(NSString *)signature + userID:(NSString *)userID + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.m b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.m new file mode 100644 index 00000000000..865b5f00983 --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.m @@ -0,0 +1,132 @@ +/* + * 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/FIRFinalizePasskeySignInRequest.h" +NS_ASSUME_NONNULL_BEGIN + +/** + @var kFinalizePasskeySignInEndPoint + @brief GCIP endpoint for finalizePasskeySignIn rpc + */ +static NSString *const kFinalizePasskeySignInEndPoint = @"accounts/passkeySignIn:finalize"; + +/** + @var kTenantIDKey + @brief The key for the tenant id value in the request. + */ +static NSString *const kTenantIDKey = @"tenantId"; + +/** + @var kAuthenticatorAuthRespKey + @brief The key for authentication response object from the authenticator. + */ +static NSString *const kAuthenticatorAuthRespKey = @"authenticatorAuthenticationResponse"; + +/** + @var kCredentialIDKey + @brief The key for registered credential identifier. + */ +static NSString *const kCredentialIDKey = @"credentialId"; + +/** + @var kAuthAssertionRespKey + @brief The key for authentication assertion from the authenticator. + */ +static NSString *const kAuthAssertionRespKey = @"authenticatorAssertionResponse"; + +/** + @var kClientDataJsonKey + @brief The key for CollectedClientData object from the authenticator. + */ +static NSString *const kClientDataJsonKey = @"clientDataJson"; + +/** + @var kAuthenticatorDataKey + @brief The key for authenticatorData from the authenticator. + */ +static NSString *const kAuthenticatorDataKey = @"authenticatorData"; + +/** + @var kSignatureKey + @brief The key for the signature from the authenticator. + */ +static NSString *const kSignatureKey = @"signature"; + +/** + @var kUserHandleKey + @brief The key for the user handle. This is the same as user ID. + */ +static NSString *const kUserHandleKey = @"userHandle"; + +@implementation FIRFinalizePasskeySignInRequest + +- (nullable instancetype)initWithCredentialID:(NSString *)credentialID + clientDataJson:(NSString *)clientDataJson + authenticatorData:(NSString *)authenticatorData + signature:(NSString *)signature + userID:(NSString *)userID + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kFinalizePasskeySignInEndPoint + requestConfiguration:requestConfiguration]; + if (self) { + self.useIdentityPlatform = YES; + _credentialID = credentialID; + _clientDataJson = clientDataJson; + _authenticatorData = authenticatorData; + _signature = signature; + _userID = userID; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Nullable *)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + NSMutableDictionary *authenticatorAuthResponse = [NSMutableDictionary dictionary]; + NSMutableDictionary *authAssertionResponse = [NSMutableDictionary dictionary]; + + if (self.tenantID) { + postBody[kTenantIDKey] = self.tenantID; + } + + if (_credentialID) { + authenticatorAuthResponse[kCredentialIDKey] = _credentialID; + } + + if (_clientDataJson) { + authAssertionResponse[kClientDataJsonKey] = _clientDataJson; + } + + if (_authenticatorData) { + authAssertionResponse[kAuthenticatorDataKey] = _authenticatorData; + } + + if (_signature) { + authAssertionResponse[kSignatureKey] = _signature; + } + + if (_userID) { + authAssertionResponse[kUserHandleKey] = _userID; + } + + authenticatorAuthResponse[kAuthAssertionRespKey] = authAssertionResponse; + postBody[kAuthenticatorAuthRespKey] = authenticatorAuthResponse; + + return [postBody copy]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h new file mode 100644 index 00000000000..ba4b8489b73 --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.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 FIRFinalizePasskeySignInResponse + @brief Represents the response from the finalizePasskeySignIn endpoint. + */ +@interface FIRFinalizePasskeySignInResponse : NSObject + +/** + @property idToken + @brief The user raw access token. + */ +@property(nonatomic, readonly, copy) NSString *idToken; + +/** + @property refershToken + @brief Refresh token for the authenticated user. + */ +@property(nonatomic, copy, readonly) NSString *refreshToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.m b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.m new file mode 100644 index 00000000000..e409c4e4ff3 --- /dev/null +++ b/FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.m @@ -0,0 +1,47 @@ +/* + * 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/FIRFinalizePasskeySignInResponse.h" + +/** + @var kIDTokenKey + @brief The name of the field in the response JSON for id token. + */ +static const NSString *kIdTokenKey = @"idToken"; + +/** + @var kRefreshTokenKey + @brief The name of the field in the response JSON for refresh token. + */ +static const NSString *kRefreshTokenKey = @"refreshToken"; + +@implementation FIRFinalizePasskeySignInResponse + +- (BOOL)setWithDictionary:(nonnull NSDictionary *)dictionary + error:(NSError *__autoreleasing _Nullable *_Nullable)error { + if (dictionary[kIdTokenKey] == nil) { + return NO; + } + if (dictionary[kRefreshTokenKey] == nil) { + return NO; + } + + _idToken = dictionary[kIdTokenKey]; + _refreshToken = dictionary[kRefreshTokenKey]; + return YES; +} + +@end diff --git a/FirebaseAuth/Tests/Unit/FIRFinalizePasskeySignInRequestTests.m b/FirebaseAuth/Tests/Unit/FIRFinalizePasskeySignInRequestTests.m new file mode 100644 index 00000000000..6c99343d843 --- /dev/null +++ b/FirebaseAuth/Tests/Unit/FIRFinalizePasskeySignInRequestTests.m @@ -0,0 +1,188 @@ +/* + * 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/FIRFinalizePasskeySignInRequest.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.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:finalize?key=APIKey"; + +/** + @var kAuthenticatorAuthRespKey + @brief The key for authentication response object from the authenticator. + */ +static NSString *const kAuthenticatorAuthRespKey = @"authenticatorAuthenticationResponse"; + +/** + @var kAuthAssertionRespKey + @brief The key for authentication assertion from the authenticator. + */ +static NSString *const kAuthAssertionRespKey = @"authenticatorAssertionResponse"; + +/** + @var kCredentialID + @brief credential ID. + */ +static NSString *const kCredentialID = @"testCredentialID"; + +/** + @var kCredentialIDKey + @brief credential ID field. + */ +static NSString *const kCredentialIDKey = @"credentialId"; + +/** + @var kRawClientDataJSON + @brief CollectedClientData object from the authenticator. + */ +static NSString *const kRawClientDataJSON = @"testRawClientDataJSON"; + +/** + @var kRawClientDataJSONKey + @brief The key for the attestation object from the authenticator. + */ +static NSString *const kRawClientDataJSONKey = @"clientDataJson"; + +/** + @var kAuthenticatorData + @brief The authenticatorData from the authenticator. + */ +static NSString *const kAuthenticatorData = @"TestAuthenticatorData"; + +/** + @var kAuthenticatorDataKey + @brief The key for authenticatorData from the authenticator. + */ +static NSString *const kAuthenticatorDataKey = @"authenticatorData"; + +/** + @var kSignature + @brief The signature from the authenticator + */ +static NSString *const kSignature = @"testSignature"; + +/** + @var kSignatureKey + @brief The key for the signature from the authenticator. + */ +static NSString *const kSignatureKey = @"signature"; + +/** + @var kUserHandle + @brief The key for the user handle. + */ +static NSString *const kUserHandle = @"testUserHandle"; + +/** + @var kUserHandleKey + @brief The key for the user handle. + */ +static NSString *const kUserHandleKey = @"userHandle"; + +/** + @class FIRFinalizePasskeySignInRequestTests + @brief Tests for @c FIRFinalizePasskeySignInRequest. + */ +@interface FIRFinalizePasskeySignInRequestTests : XCTestCase +@end + +@implementation FIRFinalizePasskeySignInRequestTests { + /** + @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)testFinalizePasskeySignInRequest { + if (@available(iOS 15.0, *)) { + FIRFinalizePasskeySignInRequest *request = + [[FIRFinalizePasskeySignInRequest alloc] initWithCredentialID:kCredentialID + clientDataJson:kRawClientDataJSON + authenticatorData:kAuthenticatorData + signature:kSignature + userID:kUserHandle + requestConfiguration:_requestConfiguration]; + + [FIRAuthBackend finalizePasskeySignIn:request + callback:^(FIRFinalizePasskeySignInResponse *_Nullable response, + NSError *_Nullable error){ + }]; + XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL); + XCTAssertNotNil(_RPCIssuer.decodedRequest); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAuthenticatorAuthRespKey][kCredentialIDKey], + kCredentialID); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAuthenticatorAuthRespKey] + [kAuthAssertionRespKey][kRawClientDataJSONKey], + kRawClientDataJSON); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAuthenticatorAuthRespKey] + [kAuthAssertionRespKey][kAuthenticatorDataKey], + kAuthenticatorData); + XCTAssertEqualObjects( + _RPCIssuer.decodedRequest[kAuthenticatorAuthRespKey][kAuthAssertionRespKey][kSignatureKey], + kSignature); + XCTAssertEqualObjects( + _RPCIssuer.decodedRequest[kAuthenticatorAuthRespKey][kAuthAssertionRespKey][kUserHandleKey], + kUserHandle); + } +} + +@end +#endif diff --git a/FirebaseAuth/Tests/Unit/FIRFinalizePasskeySignInResponseTests.m b/FirebaseAuth/Tests/Unit/FIRFinalizePasskeySignInResponseTests.m new file mode 100644 index 00000000000..cf68fd74376 --- /dev/null +++ b/FirebaseAuth/Tests/Unit/FIRFinalizePasskeySignInResponseTests.m @@ -0,0 +1,244 @@ +/* + * 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/FIRFinalizePasskeySignInRequest.h" +#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.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 kIDToken + @brief Token representing the user's identity. + */ +static NSString *const kIDToken = @"idToken"; + +/** + @var kRefreshToken + @brief Refresh Token + */ +static NSString *const kRefreshToken = @"refreshToken"; + +/** + @var kCredentialID + @brief credential ID. + */ +static NSString *const kCredentialID = @"testCredentialID"; + +/** + @var kRawClientDataJSON + @brief CollectedClientData object from the authenticator. + */ +static NSString *const kRawClientDataJSON = @"testRawClientDataJSON"; + +/** + @var kAuthenticatorData + @brief The authenticatorData from the authenticator. + */ +static NSString *const kAuthenticatorData = @"TestAuthenticatorData"; + +/** + @var kSignature + @brief The signature from the authenticator + */ +static NSString *const kSignature = @"testSignature"; + +/** + @var kUserHandle + @brief The key for the user handle. + */ +static NSString *const kUserHandle = @"testUserHandle"; + +/** + @class FIRFinalizePasskeySignInResponseTests + @brief Tests for @c FIRFinalizePasskeySignInResponse. + */ +@interface FIRFinalizePasskeySignInResponseTests : XCTestCase +@end +@implementation FIRFinalizePasskeySignInResponseTests { + /** + @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 testSuccessfulFinalizePasskeySignInResponse + @brief This test simulates a successful @c FinalizePasskeySignin flow. + */ +- (void)testSuccessfulFinalizePasskeySignInResponse { + if (@available(iOS 15.0, *)) { + FIRFinalizePasskeySignInRequest *request = + [[FIRFinalizePasskeySignInRequest alloc] initWithCredentialID:kCredentialID + clientDataJson:kRawClientDataJSON + authenticatorData:kAuthenticatorData + signature:kSignature + userID:kUserHandle + requestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRFinalizePasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend finalizePasskeySignIn:request + callback:^(FIRFinalizePasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"idToken" : kIDToken, + @"refreshToken" : kRefreshToken, + }]; + + XCTAssert(callbackInvoked); + XCTAssertNil(RPCError); + XCTAssertNotNil(RPCResponse); + XCTAssertEqualObjects(RPCResponse.idToken, kIDToken); + XCTAssertEqualObjects(RPCResponse.refreshToken, kRefreshToken); + } +} + +/** @fn testFinalizePasskeySignInResponseMissingIDTokenError + @brief This test simulates an unexpected response returned from server in @c + FinalizePasskeySignIn flow. + */ +- (void)testFinalizePasskeySignInResponseMissingIDTokenError { + if (@available(iOS 15.0, *)) { + FIRFinalizePasskeySignInRequest *request = + [[FIRFinalizePasskeySignInRequest alloc] initWithCredentialID:kCredentialID + clientDataJson:kRawClientDataJSON + authenticatorData:kAuthenticatorData + signature:kSignature + userID:kUserHandle + requestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRFinalizePasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend finalizePasskeySignIn:request + callback:^(FIRFinalizePasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"wrongkey" : @{}, + @"refreshToken" : kRefreshToken, + }]; + [self errorValidationHelperWithCallbackInvoked:callbackInvoked + rpcError:RPCError + rpcResponse:RPCResponse]; + } +} +/** @fn testFinalizePasskeySignInResponseMissingRefreshTokenError + @brief This test simulates an unexpected response returned from server in @c + FinalizePasskeySignIn flow. + */ +- (void)testFinalizePasskeySignInResponseMissingRefreshTokenError { + if (@available(iOS 15.0, *)) { + FIRFinalizePasskeySignInRequest *request = + [[FIRFinalizePasskeySignInRequest alloc] initWithCredentialID:kCredentialID + clientDataJson:kRawClientDataJSON + authenticatorData:kAuthenticatorData + signature:kSignature + userID:kUserHandle + requestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRFinalizePasskeySignInResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend finalizePasskeySignIn:request + callback:^(FIRFinalizePasskeySignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"wrongkey" : @{}, + @"idToken" : kIDToken, + }]; + [self errorValidationHelperWithCallbackInvoked:callbackInvoked + rpcError:RPCError + rpcResponse:RPCResponse]; + } +} + +/** @fn errorValidationHelperWithCallbackInvoked:rpcError:rpcResponse: + @brief Helper function to validate the unexpected response returned from server in @c + FinalizePasskeySignIn flow. + */ +- (void)errorValidationHelperWithCallbackInvoked:(BOOL)callbackInvoked + rpcError:(NSError *)RPCError + rpcResponse:(FIRFinalizePasskeySignInResponse *)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