Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Passkey #7] FIRUser startPasskeyEnrollmentWithName:completion: & finalizePasskeyEnrollmentWithPlatformCredential:completion: implementation #11991

Merged
merged 3 commits into from
Oct 24, 2023
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
1 change: 0 additions & 1 deletion FirebaseAuth/Sources/Auth/FIRAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
#import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
#import "FirebaseAuth/Sources/Utilities/FIRAuthExceptionUtils.h"
#import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
#import "FirebaseAuth/Sources/Utilities/NSData+FIRBase64.h"

#if TARGET_OS_IOS
#import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
Expand Down
50 changes: 50 additions & 0 deletions FirebaseAuth/Sources/Backend/RPC/Proto/FIRPasskeyInfo.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPasskeyInfo.h"

NS_ASSUME_NONNULL_BEGIN

/**
@var kNameKey
@brief The name of the field in the response JSON for name.
*/
static const NSString *kNameKey = @"name";

/**
@var kCredentialIdKey
@brief The name of the field in the response JSON for credential ID.
*/
static const NSString *kCredentialIdKey = @"credentialId";

@implementation FIRPasskeyInfo

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
if (dictionary[kNameKey]) {
_name = dictionary[kNameKey];
}
if (dictionary[kCredentialIdKey]) {
_credentialID = dictionary[kCredentialIdKey];
}
}
return self;
}

@end

NS_ASSUME_NONNULL_END
26 changes: 26 additions & 0 deletions FirebaseAuth/Sources/Backend/RPC/Proto/FIRPasskeyInfo_Internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 "FirebaseAuth/Sources/Backend/RPC/Proto/FIRAuthProto.h"
#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPasskeyInfo.h"

NS_ASSUME_NONNULL_BEGIN

@interface FIRPasskeyInfo () <FIRAuthProto>

@end

NS_ASSUME_NONNULL_END
39 changes: 39 additions & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIRPasskeyInfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/**
@class FIRPasskeyInfo
@brief Passkey Info
*/
NS_SWIFT_NAME(PasskeyInfo) API_UNAVAILABLE(watchos) @interface FIRPasskeyInfo : NSObject

/**
@brief Passkey name
*/
@property(nonatomic, readonly) NSString *name;

/**
@brief Passkey credential ID
*/
@property(nonatomic, readonly) NSString *credentialID;

@end

NS_ASSUME_NONNULL_END
50 changes: 50 additions & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIRUser.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@
#import "FIRAuth.h"
#import "FIRAuthDataResult.h"
#import "FIRMultiFactor.h"
#import "FIRPasskeyInfo.h"
#import "FIRUserInfo.h"

@class FIRAuthTokenResult;
@class FIRPhoneAuthCredential;
@class FIRUserProfileChangeRequest;
@class FIRUserMetadata;
@protocol FIRAuthUIDelegate;
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should TARGET_OS_VISION be included here or excluded via availability in the header?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visionOS is supported on 1.0+ beta, do you think we should include it here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we're striving to provide build support for Firebase for visionOS and community supported functionality.

Note other examples of the usage of TARGET_OS_VISION so that it builds both on Xcode 14 and Xcode 15.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Paul. Will address this in a different PR for all occurance. tracking ticket b/307595519

@class ASAuthorizationPlatformPublicKeyCredentialRegistration;
@class ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest;
#endif

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -123,6 +128,11 @@ NS_SWIFT_NAME(User)
@property(nonatomic, readonly, nonnull)
FIRMultiFactor *multiFactor API_UNAVAILABLE(macos, tvos, watchos);

/** @property enrolledPasskeys
@brief a list of user enrolled passkey object.
*/
@property(nonatomic, readonly) NSArray<FIRPasskeyInfo *> *enrolledPasskeys API_UNAVAILABLE(watchos);

/** @fn init
@brief This class should not be instantiated.
@remarks To retrieve the current user, use `Auth.currentUser`. To sign a user
Expand Down Expand Up @@ -162,6 +172,46 @@ NS_SWIFT_NAME(User)
completion:(nullable void (^)(NSError *_Nullable error))completion
NS_SWIFT_NAME(updateEmail(to:completion:));

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
/**
@fn startPasskeyEnrollmentWithName:completion:
@brief Start the passkey enrollment creating a plaform public key creation request with the
challenge from GCIP backend.

@param name The name for the passkey to be created.
@param completion Optionally; the block which is invoked when start passkey enrollment flow
finishes.

@remarks Possible error codes: // TODO @liubinj fill this after
*/
- (void)startPasskeyEnrollmentWithName:(nullable NSString *)name
completion:
(nullable void (^)(
ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest
*_Nullable request,
NSError *_Nullable error))completion
NS_SWIFT_NAME(startPasskeyEnrollment(with:completion:))
API_AVAILABLE(macos(12.0), ios(15.0), tvos(16.0));

/**
@fn finalizePasskeyEnrollmentWithPlatformCredential:completion:
@brief Finalize the passkey enrollment with the platfrom public key credential.

@param platformCredential The name for the passkey to be created.
@param completion Optionally; a block which is invoked when the finalize enroll with passkey flow
finishes, or is canceled

@remarks Possible error codes: // TODO @liubinj fill this after
*/
- (void)finalizePasskeyEnrollmentWithPlatformCredential:
(ASAuthorizationPlatformPublicKeyCredentialRegistration *)platformCredential
completion:(nullable void (^)(
FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error))completion
NS_SWIFT_NAME(finalizePasskeyEnrollment(with:completion:))
API_AVAILABLE(macos(12.0), ios(15.0), tvos(16.0));
#endif

/** @fn updatePassword:completion:
@brief Updates the password for the user. On success, the cached user profile data is updated.

Expand Down
1 change: 1 addition & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FirebaseAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#import "FIRMultiFactorSession.h"
#import "FIROAuthCredential.h"
#import "FIROAuthProvider.h"
#import "FIRPasskeyInfo.h"
#import "FIRTwitterAuthProvider.h"
#import "FIRUser.h"
#import "FIRUserInfo.h"
Expand Down
111 changes: 110 additions & 1 deletion FirebaseAuth/Sources/User/FIRUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#import "FirebaseAuth/Sources/Backend/RPC/FIRDeleteAccountResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInRequest.h"
#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/FIRGetAccountInfoRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h"
Expand All @@ -43,6 +45,8 @@
#import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeyEnrollmentRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeyEnrollmentResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
Expand All @@ -61,9 +65,12 @@
#import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"

#if TARGET_OS_IOS
#import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
#endif

#import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
#import <AuthenticationServices/AuthenticationServices.h>
#endif

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -583,6 +590,108 @@ - (void)setTokenService:(FIRSecureTokenService *)tokenService

#pragma mark -

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
- (void)startPasskeyEnrollmentWithName:(nullable NSString *)name
completion:
(nullable void (^)(
ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest
*_Nullable request,
NSError *_Nullable error))completion {
FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;

FIRStartPasskeyEnrollmentRequest *request =
[[FIRStartPasskeyEnrollmentRequest alloc] initWithIDToken:self.rawAccessToken
requestConfiguration:requestConfiguration];
[FIRAuthBackend
startPasskeyEnrollment:request
callback:^(FIRStartPasskeyEnrollmentResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
completion(nil, error);
return;
} else {
// cached the passkey name. This is needed when calling
// finalizePasskeyEnrollment
self.passkeyName = name;
NSData *challengeInData =
[[NSData alloc] initWithBase64EncodedString:response.challenge
options:0];
NSData *userIdInData =
[[NSData alloc] initWithBase64EncodedString:response.userID options:0];

ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
[[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
initWithRelyingPartyIdentifier:response.rpID];
ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest *request =
[provider
createCredentialRegistrationRequestWithChallenge:challengeInData
name:name
userID:userIdInData];
completion(request, nil);
}
}];
}

- (void)finalizePasskeyEnrollmentWithPlatformCredential:
(ASAuthorizationPlatformPublicKeyCredentialRegistration *)platformCredential
completion:(nullable void (^)(
FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error))completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
FIRAuthDataResultCallback decoratedCallback =
[FIRAuth.auth signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;

NSString *credentialID = [platformCredential.credentialID base64EncodedStringWithOptions:0];
renkelvin marked this conversation as resolved.
Show resolved Hide resolved
NSString *clientDataJson =
[platformCredential.rawClientDataJSON base64EncodedStringWithOptions:0];
NSString *attestationObject =
[platformCredential.rawAttestationObject base64EncodedStringWithOptions:0];
// If passkey name is not provided, we will provide a firebase formatted default name.

if (self.passkeyName != nil || [self.passkeyName isEqual:@""]) {
self.passkeyName = @"Unnamed account (Apple)";
}
FIRFinalizePasskeyEnrollmentRequest *request =
[[FIRFinalizePasskeyEnrollmentRequest alloc] initWithIDToken:self.rawAccessToken
name:self.passkeyName
credentialID:credentialID
clientDataJson:clientDataJson
attestationObject:attestationObject
requestConfiguration:requestConfiguration];

[FIRAuthBackend
finalizePasskeyEnrollment:request
callback:^(FIRFinalizePasskeyEnrollmentResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
decoratedCallback(nil, error);
} else {
[FIRAuth.auth
completeSignInWithAccessToken:response.idToken
accessTokenExpirationDate:nil
refreshToken:response.refreshToken
anonymous:NO
callback:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
completion(nil, error);
return;
}

FIRAuthDataResult *authDataResult =
user ? [[FIRAuthDataResult alloc]
initWithUser:user
additionalUserInfo:nil]
: nil;
decoratedCallback(authDataResult, error);
}];
}
}];
});
}
#endif // #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST

/** @fn updateEmail:password:callback:
@brief Updates email address and/or password for the current user.
@remarks May fail if there is already an email/password-based account for the same email
Expand Down
6 changes: 6 additions & 0 deletions FirebaseAuth/Sources/User/FIRUser_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ typedef void (^FIRVerifyBeforeUpdateEmailCallback)(NSError *_Nullable error);
*/
@property(nonatomic, copy, readonly) NSString *rawAccessToken;

/**
@property passkeyName
@brief A cached passkey name that being passed from startPasskeyEnrollmentWithName:completion: call
and consumed at finalizePasskeyEnrollmentWithPlatformCredential:completion: call
*/
@property(nonatomic, copy, nullable) NSString *passkeyName;
/** @property auth
@brief A weak reference to a FIRAuth instance associated with this instance.
*/
Expand Down
Loading
Loading