diff --git a/FirebaseAuth/Tests/Sample/AuthSample.xcodeproj/project.pbxproj b/FirebaseAuth/Tests/Sample/AuthSample.xcodeproj/project.pbxproj index 8eff016dc36..b028be6e69d 100644 --- a/FirebaseAuth/Tests/Sample/AuthSample.xcodeproj/project.pbxproj +++ b/FirebaseAuth/Tests/Sample/AuthSample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0274FD322BB5DCF000DE5140 /* MainViewController+Passkey.m in Sources */ = {isa = PBXBuildFile; fileRef = 0274FD312BB5DCF000DE5140 /* MainViewController+Passkey.m */; }; 400283EA23EA254B0006A298 /* MainViewController+MultiFactor.m in Sources */ = {isa = PBXBuildFile; fileRef = 400283E923EA254A0006A298 /* MainViewController+MultiFactor.m */; }; DE1865AC245B879B00F8AD70 /* TestsBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1865AB245B879B00F8AD70 /* TestsBase.swift */; }; DE1865AE245B8A1400F8AD70 /* AnonymousTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1865AD245B8A1400F8AD70 /* AnonymousTests.swift */; }; @@ -85,6 +86,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 0274FD302BB5DCF000DE5140 /* MainViewController+Passkey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MainViewController+Passkey.h"; sourceTree = ""; }; + 0274FD312BB5DCF000DE5140 /* MainViewController+Passkey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MainViewController+Passkey.m"; sourceTree = ""; }; 400283E823EA254A0006A298 /* MainViewController+MultiFactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MainViewController+MultiFactor.h"; sourceTree = ""; }; 400283E923EA254A0006A298 /* MainViewController+MultiFactor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MainViewController+MultiFactor.m"; sourceTree = ""; }; DE1865AB245B879B00F8AD70 /* TestsBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestsBase.swift; sourceTree = ""; }; @@ -277,6 +280,8 @@ DE800B4222A2F8AF00AC9A23 /* MainViewController+GameCenter.h */, DE800B1922A2F8AF00AC9A23 /* MainViewController+GameCenter.m */, DE800B2122A2F8AF00AC9A23 /* MainViewController+Google.h */, + 0274FD302BB5DCF000DE5140 /* MainViewController+Passkey.h */, + 0274FD312BB5DCF000DE5140 /* MainViewController+Passkey.m */, DE800B3922A2F8AF00AC9A23 /* MainViewController+Google.m */, DE800B3022A2F8AF00AC9A23 /* MainViewController+Internal.h */, 400283E823EA254A0006A298 /* MainViewController+MultiFactor.h */, @@ -534,6 +539,7 @@ DE800B5A22A2F8AF00AC9A23 /* MainViewController+App.m in Sources */, DE800B6222A2F8AF00AC9A23 /* CustomTokenDataEntryViewController.m in Sources */, DE800B6122A2F8AF00AC9A23 /* ApplicationDelegate.m in Sources */, + 0274FD322BB5DCF000DE5140 /* MainViewController+Passkey.m in Sources */, DE800B4A22A2F8AF00AC9A23 /* GoogleAuthProvider.m in Sources */, DE800B5B22A2F8AF00AC9A23 /* MainViewController+OAuth.m in Sources */, DE800B6022A2F8AF00AC9A23 /* FacebookAuthProvider.m in Sources */, diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m index 194a54064c4..c25f72a78ee 100644 --- a/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m @@ -434,53 +434,76 @@ - (void)revokeAppleTokenAndDeleteUser { } - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) { - ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential; - NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]]; - FIROAuthCredential *credential = - [FIROAuthProvider appleCredentialWithIDToken:IDToken - rawNonce:self.appleRawNonce - fullName:appleIDCredential.fullName]; - - if ([appleIDCredential.state isEqualToString:@"signIn"]) { - [FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { - if (!error) { - NSLog(@"%@", authResult.description); - } else { - NSLog(@"%@", error.description); - } - }]; - } else if ([appleIDCredential.state isEqualToString:@"link"]) { - [FIRAuth.auth.currentUser linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { - if (!error) { - NSLog(@"%@", authResult.description); - } else { - NSLog(@"%@", error.description); - } - }]; - } else if ([appleIDCredential.state isEqualToString:@"reauth"]) { - [FIRAuth.auth.currentUser reauthenticateWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { - if (!error) { - NSLog(@"%@", authResult.description); - } else { - NSLog(@"%@", error.description); - } - }]; - } else if ([appleIDCredential.state isEqualToString:@"revokeAppleTokenAndDeleteUser"]) { - NSString *code = [[NSString alloc] initWithData:appleIDCredential.authorizationCode encoding:NSUTF8StringEncoding]; + if (@available(iOS 16.0, *)) { + if ([authorization.credential isKindOfClass: [ASAuthorizationPlatformPublicKeyCredentialRegistration class]]) { + ASAuthorizationPlatformPublicKeyCredentialRegistration *platformCredential = (ASAuthorizationPlatformPublicKeyCredentialRegistration*) authorization.credential; + FIRUser *user = FIRAuth.auth.currentUser; - [FIRAuth.auth revokeTokenWithAuthorizationCode:code completion:^(NSError * _Nullable error) { + [user finalizePasskeyEnrollmentWithPlatformCredential:platformCredential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + [self log:[NSString stringWithFormat:@"Passkey Enrollment succeed with uid: %@", authResult.user.uid] ]; + [self showTypicalUIForUserUpdateResultsWithTitle:@"Enrollment with passkey" error:error]; + }]; + + } else if ([authorization.credential isKindOfClass: [ASAuthorizationPlatformPublicKeyCredentialAssertion class]]) { + ASAuthorizationPlatformPublicKeyCredentialAssertion *platformCredential = (ASAuthorizationPlatformPublicKeyCredentialAssertion*) authorization.credential; + [[AppManager auth] finalizePasskeySignInWithPlatformCredential:platformCredential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + [self log:[NSString stringWithFormat:@"Passkey sign-in succeed with uid: %@", authResult.user.uid]]; + [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign in with passkey" error:error]; + + }]; + } + } else if ([authorization.credential isKindOfClass: [ASAuthorizationAppleIDCredential class]]) { + ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential; + NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]]; + FIROAuthCredential *credential = + [FIROAuthProvider appleCredentialWithIDToken:IDToken + rawNonce:self.appleRawNonce + fullName:appleIDCredential.fullName]; + + if ([appleIDCredential.state isEqualToString:@"signIn"]) { + [FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (!error) { - // Token revocation succeeded then delete user again. - [user deleteWithCompletion:^(NSError *_Nullable error) { - if (error) { - [self logFailure:@"delete account failed" error:error]; - } - [self showTypicalUIForUserUpdateResultsWithTitle:@"Delete User" error:error]; - }]; + NSLog(@"%@", authResult.description); } else { NSLog(@"%@", error.description); } - }]; + }]; + } else if ([appleIDCredential.state isEqualToString:@"link"]) { + [FIRAuth.auth.currentUser linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + if (!error) { + NSLog(@"%@", authResult.description); + } else { + NSLog(@"%@", error.description); + } + }]; + } else if ([appleIDCredential.state isEqualToString:@"reauth"]) { + [FIRAuth.auth.currentUser reauthenticateWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + if (!error) { + NSLog(@"%@", authResult.description); + } else { + NSLog(@"%@", error.description); + } + }]; + } else if ([appleIDCredential.state isEqualToString:@"revokeAppleTokenAndDeleteUser"]) { + NSString *code = [[NSString alloc] initWithData:appleIDCredential.authorizationCode encoding:NSUTF8StringEncoding]; + FIRUser *user = FIRAuth.auth.currentUser; + [FIRAuth.auth revokeTokenWithAuthorizationCode:code completion:^(NSError * _Nullable error) { + if (!error) { + // Token revocation succeeded then delete user again. + [user deleteWithCompletion:^(NSError *_Nullable error) { + if (error) { + [self logFailure:@"delete account failed" error:error]; + } + [self showTypicalUIForUserUpdateResultsWithTitle:@"Delete User" error:error]; + }]; + } else { + NSLog(@"%@", error.description); + } + }]; + } + } else { + // More supported credential type can be added here. + [self log:@"credential type not found or OS version too low."]; } } diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h new file mode 100644 index 00000000000..d253e562138 --- /dev/null +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h @@ -0,0 +1,31 @@ +/* + * 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 + +#import "MainViewController.h" + +#import "StaticContentTableViewManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MainViewController (Passkey) + +- (StaticContentTableViewSection *)passkeySection; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m new file mode 100644 index 00000000000..8994f968d96 --- /dev/null +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m @@ -0,0 +1,118 @@ +/* + * 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 "MainViewController+Passkey.h" +#import "AppManager.h" +#import "MainViewController+Internal.h" +#import + + +NS_ASSUME_NONNULL_BEGIN +@interface MainViewController () +@end + +@implementation MainViewController (Passkey) + +- (StaticContentTableViewSection *)passkeySection { + __weak typeof(self) weakSelf = self; + return [StaticContentTableViewSection sectionWithTitle:@"Passkey" cells:@[ + [StaticContentTableViewCell cellWithTitle:@"Sign Up With Passkey" + action:^{ [weakSelf passkeySignUp]; }], + [StaticContentTableViewCell cellWithTitle:@"Sign In With Passkey" + action:^{ [weakSelf passkeySignIn]; }], + [StaticContentTableViewCell cellWithTitle:@"Enroll with Passkey" + action:^{ [weakSelf passkeyEnroll]; }], + [StaticContentTableViewCell cellWithTitle:@"Unenroll with Passkey" + action:^{ [weakSelf passkeyUnenroll]; }], + ]]; +} + +- (void)passkeySignUp { + // sign in anoymously + [[AppManager auth] signInAnonymouslyWithCompletion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { + if (error) { + [self logFailure:@"sign-in anonymously failed" error:error]; + } else { + [self logSuccess:@"sign-in anonymously succeeded."]; + [self log:[NSString stringWithFormat:@"User ID : %@", result.user.uid]]; + [self passkeyEnroll]; + } + }]; +} + +- (void)passkeyEnroll { + FIRUser *user = FIRAuth.auth.currentUser; + if (!user) { + [self logFailure:@"Please sign in first." error:nil]; + return; + } + [self showTextInputPromptWithMessage:@"passkey name" + keyboardType:UIKeyboardTypeEmailAddress + completionBlock:^(BOOL userPressedOK, NSString *_Nullable passkeyName) { + if (@available(iOS 16.0, macOS 12.0, tvOS 16.0, *)) { + [user startPasskeyEnrollmentWithName:passkeyName completion:^(ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest * _Nullable request, NSError * _Nullable error) { + if (request) { + ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests: [NSMutableArray arrayWithObject:request]]; + controller.delegate = self; + controller.presentationContextProvider = self; + [controller performRequests]; + } else if (error) { + [self logFailure:@"Passkey enrollment failed" error:error]; + } + }]; + } else { + [self log:@"OS version is not supported for this action."]; + } + }]; + +} + +- (void)passkeySignIn { + if (@available(iOS 16.0, macOS 12.0, tvOS 16.0, *)) { + [[AppManager auth] startPasskeySignInWithCompletion:^(ASAuthorizationPlatformPublicKeyCredentialAssertionRequest * _Nullable request, NSError * _Nullable error) { + if (request) { + ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests: [NSMutableArray arrayWithObject:request]]; + controller.delegate = self; + controller.presentationContextProvider = self; + [controller performRequestsWithOptions:ASAuthorizationControllerRequestOptionPreferImmediatelyAvailableCredentials]; + } + }]; + } else { + [self log:@"OS version is not supported for this action."]; + } +} + +- (void)passkeyUnenroll { + FIRUser *user = FIRAuth.auth.currentUser; + if (!user) { + [self logFailure:@"Please sign in first." error:nil]; + return; + } + [self showTextInputPromptWithMessage:@"passkey credential ID" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable credentialID) { + [user unenrollPasskeyWithCredentialID:credentialID completion:^(NSError * _Nullable error) { + if (error) { + [self logFailure:[NSString stringWithFormat:@"Withdraw passkey with credential ID: %@ failed", credentialID] error:error]; + } else { + [self logSuccess:[NSString stringWithFormat:@"Withdraw passkey with credential ID: %@ succeed", credentialID]]; + } + }]; + }]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController.m index 1b53c911a86..351c4d03e43 100644 --- a/FirebaseAuth/Tests/Sample/Sample/MainViewController.m +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController.m @@ -40,7 +40,8 @@ #import "UIViewController+Alerts.h" #import "UserInfoViewController.h" #import "UserTableViewCell.h" - +#import "MainViewController+Passkey.h" +#import NS_ASSUME_NONNULL_BEGIN static NSString *const kSectionTitleSettings = @"Settings"; @@ -250,6 +251,8 @@ - (void)updateTable { [weakSelf appSection], // OOB [weakSelf oobSection], + // Passkey + [weakSelf passkeySection], // Auto Tests [weakSelf autoTestsSection], ]]; @@ -638,6 +641,10 @@ - (IBAction)copyConsole:(id)sender { pasteboard.string = _consoleString ?: @""; } +- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ + return self.view.window; +} + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m b/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m index fbf44ae9634..5ca334816a4 100644 --- a/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m +++ b/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m @@ -17,6 +17,7 @@ #import "UserInfoViewController.h" #import +#import #import #import #import "StaticContentTableViewManager.h" @@ -74,6 +75,8 @@ - (void)loadTableView { value:stringWithBool(_user.emailVerified)], [StaticContentTableViewCell cellWithTitle:@"refresh token" value:_user.refreshToken], [StaticContentTableViewCell cellWithTitle:@"multi factor" value:[self multiFactorString]], + [StaticContentTableViewCell cellWithTitle:@"passkeys" value:[self passkeysString]], + ]] ] mutableCopy]; [sections addObject:[self sectionWithUserInfo:_user]]; @@ -108,4 +111,17 @@ - (NSString *)multiFactorString { return string; } +- (NSString *)passkeysString { + NSMutableString *string = [NSMutableString string]; + + for (FIRPasskeyInfo *info in _user.enrolledPasskeys) { + [string appendString:info.name]; + [string appendString:@" - "]; + [string appendString:info.credentialID]; + [string appendString:@" "]; + } + + return string; +} + @end