From 6b85767c981767007e4f6fe9b4675b3428407b49 Mon Sep 17 00:00:00 2001 From: Zachary Orr <516458+ZachOrr@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:48:09 -0400 Subject: [PATCH] Add VERY BASIC Sign In with Apple --- .../MyTBAKit/Classes/MyTBAOperation.swift | 5 +- the-blue-alliance-ios.entitlements | 4 + .../project.pbxproj | 91 ++++++++++++++++- .../AppDelegate/AppDelegate.swift | 8 +- the-blue-alliance-ios/StatusDefaults.plist | 4 +- .../MyTBA/MyTBASignInViewController.swift | 99 ++++++++++++++++++- .../MyTBA/MyTBASignInViewController.xib | 97 +++++++++++------- 7 files changed, 263 insertions(+), 45 deletions(-) diff --git a/Packages/MyTBAKit/Sources/MyTBAKit/Classes/MyTBAOperation.swift b/Packages/MyTBAKit/Sources/MyTBAKit/Classes/MyTBAOperation.swift index 16b8a86c7..4511c2bd1 100644 --- a/Packages/MyTBAKit/Sources/MyTBAKit/Classes/MyTBAOperation.swift +++ b/Packages/MyTBAKit/Sources/MyTBAKit/Classes/MyTBAOperation.swift @@ -10,7 +10,10 @@ public class MyTBAOperation: TBAOperation { let request = myTBA.createRequest(method, bodyData) task = myTBA.urlSession.dataTask(with: request) { [weak self] (data: Data?, response: URLResponse?, error: Error?) in - if let error = error { + if let response = response as? HTTPURLResponse, response.statusCode == 500 { + // Catch 500s as errors + completion(nil, MyTBAError.error(500, "Internal server error")) + } else if let error = error { completion(nil, error) } else if let data = data { #if DEBUG diff --git a/the-blue-alliance-ios.entitlements b/the-blue-alliance-ios.entitlements index 56982d6c6..ddc7da992 100644 --- a/the-blue-alliance-ios.entitlements +++ b/the-blue-alliance-ios.entitlements @@ -4,6 +4,10 @@ aps-environment production + com.apple.developer.applesignin + + Default + com.apple.developer.associated-domains activitycontinuation:www.thebluealliance.com diff --git a/the-blue-alliance-ios.xcodeproj/project.pbxproj b/the-blue-alliance-ios.xcodeproj/project.pbxproj index e52ffe850..b169d9daf 100644 --- a/the-blue-alliance-ios.xcodeproj/project.pbxproj +++ b/the-blue-alliance-ios.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -194,6 +194,13 @@ 92DF54E121B1B65A0082C58C /* StatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DF54E021B1B65A0082C58C /* StatusService.swift */; }; 92E9F0AB216C439700C1F916 /* NoDataViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92E9F0AA216C439700C1F916 /* NoDataViewController.swift */; }; 92F481F11E7DA04B00B4ED7E /* EventsContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F481F01E7DA04B00B4ED7E /* EventsContainerViewController.swift */; }; + 92F63E9B2BA91B4F0025CC03 /* MyTBAKit in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63E9A2BA91B4F0025CC03 /* MyTBAKit */; }; + 92F63E9E2BA91BCA0025CC03 /* Search in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63E9D2BA91BCA0025CC03 /* Search */; }; + 92F63EA12BA91BD80025CC03 /* TBAData in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63EA02BA91BD80025CC03 /* TBAData */; }; + 92F63EA42BA91BEC0025CC03 /* TBAKit in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63EA32BA91BEC0025CC03 /* TBAKit */; }; + 92F63EA72BA91BFB0025CC03 /* TBAOperation in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63EA62BA91BFB0025CC03 /* TBAOperation */; }; + 92F63EAA2BA91C070025CC03 /* TBAProtocols in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63EA92BA91C070025CC03 /* TBAProtocols */; }; + 92F63EAD2BA91C170025CC03 /* TBAUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 92F63EAC2BA91C170025CC03 /* TBAUtils */; }; 92F79A7623D6536A0026E9E8 /* SearchContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F79A7523D6536A0026E9E8 /* SearchContainer.swift */; }; 92F8E356209AAE890094213F /* PushService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F8E355209AAE890094213F /* PushService.swift */; }; 92FA4D2D228606BB00030BA3 /* TeamHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FA4D2C228606BB00030BA3 /* TeamHeaderView.swift */; }; @@ -513,12 +520,19 @@ 924BDD9026D2B366008BE7D2 /* FirebaseMessaging in Frameworks */, 9202400D2AEB6ADC0003D118 /* MyTBAKit in Frameworks */, 924BDD8E26D2B366008BE7D2 /* FirebaseCrashlytics in Frameworks */, + 92F63EA12BA91BD80025CC03 /* TBAData in Frameworks */, 920240112AEB6AE60003D118 /* TBAData in Frameworks */, 920240072AEB6AAD0003D118 /* PureLayout in Frameworks */, + 92F63EA72BA91BFB0025CC03 /* TBAOperation in Frameworks */, 920240092AEB6AC30003D118 /* TBAOperation in Frameworks */, 924BDD8A26D2B366008BE7D2 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, + 92F63EAD2BA91C170025CC03 /* TBAUtils in Frameworks */, 9202400F2AEB6ADC0003D118 /* TBAKit in Frameworks */, + 92F63E9B2BA91B4F0025CC03 /* MyTBAKit in Frameworks */, + 92F63E9E2BA91BCA0025CC03 /* Search in Frameworks */, 92C5FBDF2B0038250006AE48 /* Agrume in Frameworks */, + 92F63EAA2BA91C070025CC03 /* TBAProtocols in Frameworks */, + 92F63EA42BA91BEC0025CC03 /* TBAKit in Frameworks */, 924BDD9226D2B366008BE7D2 /* FirebaseRemoteConfig in Frameworks */, 924BDD9526D2B391008BE7D2 /* GoogleSignIn in Frameworks */, ); @@ -1420,6 +1434,13 @@ 920240102AEB6AE60003D118 /* TBAData */, 92C5FBDB2B0037E10006AE48 /* YouTubeiOSPlayerHelper */, 92C5FBDE2B0038250006AE48 /* Agrume */, + 92F63E9A2BA91B4F0025CC03 /* MyTBAKit */, + 92F63E9D2BA91BCA0025CC03 /* Search */, + 92F63EA02BA91BD80025CC03 /* TBAData */, + 92F63EA32BA91BEC0025CC03 /* TBAKit */, + 92F63EA62BA91BFB0025CC03 /* TBAOperation */, + 92F63EA92BA91C070025CC03 /* TBAProtocols */, + 92F63EAC2BA91C170025CC03 /* TBAUtils */, ); productName = "the-blue-alliance"; productReference = 92942D921E2154DA008E79CA /* The Blue Alliance.app */; @@ -1493,6 +1514,13 @@ 92B3217D26ED1A79003B28DC /* XCRemoteSwiftPackageReference "PureLayout" */, 92C5FBDA2B0037E10006AE48 /* XCRemoteSwiftPackageReference "youtube-ios-player-helper" */, 92C5FBDD2B0038250006AE48 /* XCRemoteSwiftPackageReference "Agrume" */, + 92F63E992BA91B4F0025CC03 /* XCLocalSwiftPackageReference "Packages/MyTBAKit" */, + 92F63E9C2BA91BCA0025CC03 /* XCLocalSwiftPackageReference "Packages/Search" */, + 92F63E9F2BA91BD80025CC03 /* XCLocalSwiftPackageReference "Packages/TBAData" */, + 92F63EA22BA91BEC0025CC03 /* XCLocalSwiftPackageReference "Packages/TBAKit" */, + 92F63EA52BA91BFB0025CC03 /* XCLocalSwiftPackageReference "Packages/TBAOperation" */, + 92F63EA82BA91C070025CC03 /* XCLocalSwiftPackageReference "Packages/TBAProtocols" */, + 92F63EAB2BA91C170025CC03 /* XCLocalSwiftPackageReference "Packages/TBAUtils" */, ); productRefGroup = 92942D931E2154DA008E79CA /* Products */; projectDirPath = ""; @@ -2159,13 +2187,44 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 92F63E992BA91B4F0025CC03 /* XCLocalSwiftPackageReference "Packages/MyTBAKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/MyTBAKit; + }; + 92F63E9C2BA91BCA0025CC03 /* XCLocalSwiftPackageReference "Packages/Search" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/Search; + }; + 92F63E9F2BA91BD80025CC03 /* XCLocalSwiftPackageReference "Packages/TBAData" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/TBAData; + }; + 92F63EA22BA91BEC0025CC03 /* XCLocalSwiftPackageReference "Packages/TBAKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/TBAKit; + }; + 92F63EA52BA91BFB0025CC03 /* XCLocalSwiftPackageReference "Packages/TBAOperation" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/TBAOperation; + }; + 92F63EA82BA91C070025CC03 /* XCLocalSwiftPackageReference "Packages/TBAProtocols" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/TBAProtocols; + }; + 92F63EAB2BA91C170025CC03 /* XCLocalSwiftPackageReference "Packages/TBAUtils" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/TBAUtils; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ 924BDD8826D2B366008BE7D2 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 7.0.0; + minimumVersion = 10.0.0; }; }; 924BDD9326D2B391008BE7D2 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { @@ -2276,6 +2335,34 @@ package = 92C5FBDD2B0038250006AE48 /* XCRemoteSwiftPackageReference "Agrume" */; productName = Agrume; }; + 92F63E9A2BA91B4F0025CC03 /* MyTBAKit */ = { + isa = XCSwiftPackageProductDependency; + productName = MyTBAKit; + }; + 92F63E9D2BA91BCA0025CC03 /* Search */ = { + isa = XCSwiftPackageProductDependency; + productName = Search; + }; + 92F63EA02BA91BD80025CC03 /* TBAData */ = { + isa = XCSwiftPackageProductDependency; + productName = TBAData; + }; + 92F63EA32BA91BEC0025CC03 /* TBAKit */ = { + isa = XCSwiftPackageProductDependency; + productName = TBAKit; + }; + 92F63EA62BA91BFB0025CC03 /* TBAOperation */ = { + isa = XCSwiftPackageProductDependency; + productName = TBAOperation; + }; + 92F63EA92BA91C070025CC03 /* TBAProtocols */ = { + isa = XCSwiftPackageProductDependency; + productName = TBAProtocols; + }; + 92F63EAC2BA91C170025CC03 /* TBAUtils */ = { + isa = XCSwiftPackageProductDependency; + productName = TBAUtils; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/the-blue-alliance-ios/AppDelegate/AppDelegate.swift b/the-blue-alliance-ios/AppDelegate/AppDelegate.swift index 608dfe879..22276666e 100755 --- a/the-blue-alliance-ios/AppDelegate/AppDelegate.swift +++ b/the-blue-alliance-ios/AppDelegate/AppDelegate.swift @@ -170,8 +170,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - // Kickoff background myTBA/Google sign in, along with setting up delegates - setupGoogleAuthentication() + // Kickoff background myTBA, along with setting up delegates + setupPreviousAuthentication() // Our app setup operation will load our persistent stores, propogate persistance container let appSetupOperation = AppSetupOperation(indexDelegate: indexDelegate, persistentContainer: persistentContainer, tbaKit: tbaKit, userDefaults: userDefaults) @@ -294,9 +294,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { myTBA.authenticationProvider.add(observer: pushService) } - private func setupGoogleAuthentication() { + private func setupPreviousAuthentication() { // If we're authenticated with Google but don't have a Firebase user, get a Firebase user if Auth.auth().currentUser == nil, GIDSignIn.sharedInstance.hasPreviousSignIn() { + // TODO: Need to figure out if it's a Google user or an Apple user? + // I suppose, let's print something here... GIDSignIn.sharedInstance.restorePreviousSignIn { [unowned self] user, error in if let error = error { errorRecorder.record(error) diff --git a/the-blue-alliance-ios/StatusDefaults.plist b/the-blue-alliance-ios/StatusDefaults.plist index 836bf5d47..47d578c5c 100644 --- a/the-blue-alliance-ios/StatusDefaults.plist +++ b/the-blue-alliance-ios/StatusDefaults.plist @@ -3,12 +3,12 @@ max_season - 2022 + 2024 min_app_version -1 latest_app_version -1 current_season - 2022 + 2024 diff --git a/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.swift b/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.swift index b2cbf00cf..911c75bc5 100644 --- a/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.swift +++ b/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.swift @@ -1,3 +1,5 @@ +import AuthenticationServices +import CryptoKit import GoogleSignIn import FirebaseAuth import Foundation @@ -9,7 +11,7 @@ protocol SignInViewControllerDelegate: AnyObject { func pushRegistrationError(error: Error) } -class MyTBASignInViewController: UIViewController { +class MyTBASignInViewController: UIViewController, ASAuthorizationControllerPresentationContextProviding { @IBOutlet var starImageView: UIImageView! { didSet { @@ -22,6 +24,9 @@ class MyTBASignInViewController: UIViewController { weak var delegate: SignInViewControllerDelegate? + // Unhashed nonce. + fileprivate var currentNonce: String? + init() { super.init(nibName: String(describing: type(of: self)), bundle: Bundle.main) } @@ -108,4 +113,96 @@ class MyTBASignInViewController: UIViewController { } } + // https://firebase.google.com/docs/auth/ios/apple + + private func sha256(_ input: String) -> String { + let inputData = Data(input.utf8) + let hashedData = SHA256.hash(data: inputData) + let hashString = hashedData.compactMap { + String(format: "%02x", $0) + }.joined() + + return hashString + } + + @IBAction private func signInWithApple() { + let nonce = randomNonceString() + currentNonce = nonce + let appleIDProvider = ASAuthorizationAppleIDProvider() + let request = appleIDProvider.createRequest() + request.requestedScopes = [.fullName, .email] + request.nonce = sha256(nonce) + + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.presentationContextProvider = self + authorizationController.performRequests() + } + + private func randomNonceString(length: Int = 32) -> String { + precondition(length > 0) + var randomBytes = [UInt8](repeating: 0, count: length) + let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes) + if errorCode != errSecSuccess { + fatalError( + "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)" + ) + } + + let charset: [Character] = + Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") + + let nonce = randomBytes.map { byte in + // Pick a random character from the set, wrapping around if needed. + charset[Int(byte) % charset.count] + } + + return String(nonce) + } + +} + +extension MyTBASignInViewController: ASAuthorizationControllerDelegate { + + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return view.window! + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { + guard let nonce = currentNonce else { + fatalError("Invalid state: A login callback was received, but no login request was sent.") + } + guard let appleIDToken = appleIDCredential.identityToken else { + print("Unable to fetch identity token") + return + } + guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { + print("Unable to serialize token string from data: \(appleIDToken.debugDescription)") + return + } + // Initialize a Firebase credential, including the user's full name. + let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, + rawNonce: nonce, + fullName: appleIDCredential.fullName) + // Sign in with Firebase. + Auth.auth().signIn(with: credential) { (authResult, error) in + if let error { + // Error. If error.code == .MissingOrInvalidNonce, make sure + // you're sending the SHA256-hashed nonce as a hex string with + // your request to Apple. + print(error.localizedDescription) + return + } + + // TODO: Need to do something here...... + } + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + // Handle error. + print("Sign in with Apple errored: \(error)") + } + } diff --git a/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.xib b/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.xib index e2f1811fe..d21347f37 100644 --- a/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.xib +++ b/the-blue-alliance-ios/ViewControllers/MyTBA/MyTBASignInViewController.xib @@ -1,9 +1,11 @@ - + - + + + @@ -22,20 +24,20 @@ - + - + - + - + - + - + - + - + + + + + + + + - + - + + @@ -112,17 +132,22 @@ - - + - - + + - - + + + + + + + +