diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 173eca3..71d383a 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 798AD52129072C22001B4801 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798AD52029072C22001B4801 /* AppView.swift */; }; 798AD5232907309C001B4801 /* SessionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798AD5222907309C001B4801 /* SessionView.swift */; }; 798AD525290731A0001B4801 /* AuthWithEmailAndPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798AD524290731A0001B4801 /* AuthWithEmailAndPasswordView.swift */; }; + 79C17A802A589EC000C67A61 /* SignInWithAppleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C17A7F2A589EC000C67A61 /* SignInWithAppleView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -26,6 +27,7 @@ 798AD52029072C22001B4801 /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; 798AD5222907309C001B4801 /* SessionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionView.swift; sourceTree = ""; }; 798AD524290731A0001B4801 /* AuthWithEmailAndPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthWithEmailAndPasswordView.swift; sourceTree = ""; }; + 79C17A7F2A589EC000C67A61 /* SignInWithAppleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInWithAppleView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -83,6 +85,7 @@ 18DE589E286B2C77005F7FF2 /* ExamplesApp.swift */, 1820720D286B449B0009D0BF /* Secrets.swift */, 798AD5222907309C001B4801 /* SessionView.swift */, + 79C17A7F2A589EC000C67A61 /* SignInWithAppleView.swift */, ); path = Sources; sourceTree = ""; @@ -164,6 +167,7 @@ 798AD52129072C22001B4801 /* AppView.swift in Sources */, 798AD525290731A0001B4801 /* AuthWithEmailAndPasswordView.swift in Sources */, 798AD5232907309C001B4801 /* SessionView.swift in Sources */, + 79C17A802A589EC000C67A61 /* SignInWithAppleView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/Shared/Examples.entitlements b/Examples/Shared/Examples.entitlements index 225aa48..09f7cea 100644 --- a/Examples/Shared/Examples.entitlements +++ b/Examples/Shared/Examples.entitlements @@ -2,6 +2,10 @@ + com.apple.developer.applesignin + + Default + com.apple.security.app-sandbox com.apple.security.network.client diff --git a/Examples/Shared/Sources/AppView.swift b/Examples/Shared/Sources/AppView.swift index a0cf5db..a285fff 100644 --- a/Examples/Shared/Sources/AppView.swift +++ b/Examples/Shared/Sources/AppView.swift @@ -16,6 +16,10 @@ struct AppView: View { NavigationLink("Auth with Email and Password") { AuthWithEmailAndPasswordView() } + + NavigationLink("Sign in with Apple") { + SignInWithAppleView() + } } .listStyle(.plain) .navigationTitle("Examples") diff --git a/Examples/Shared/Sources/SignInWithAppleView.swift b/Examples/Shared/Sources/SignInWithAppleView.swift new file mode 100644 index 0000000..7f34f4a --- /dev/null +++ b/Examples/Shared/Sources/SignInWithAppleView.swift @@ -0,0 +1,87 @@ +// +// SignInWithAppleView.swift +// Examples +// +// Created by Guilherme Souza on 07/07/23. +// + +import AuthenticationServices +import CryptoKit +import SwiftUI +@_spi(Experimental) import GoTrue + +struct SignInWithAppleView: View { + @Environment(\.goTrueClient) private var client + @State var nonce: String? + + var body: some View { + SignInWithAppleButton { request in +// self.nonce = sha256(randomString()) +// request.nonce = nonce + request.requestedScopes = [.email, .fullName] + } onCompletion: { result in + Task { + do { + guard let credential = try result.get().credential as? ASAuthorizationAppleIDCredential + else { + return + } + + guard let idToken = credential.identityToken + .flatMap({ String(data: $0, encoding: .utf8) }) + else { + return + } + + try await client.signInWithIdToken( + credentials: .init( + provider: .apple, + idToken: idToken/*, + nonce: self.nonce*/ + ) + ) + } catch { + dump(error) + } + } + } + .fixedSize() + } +} + +func randomString(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) +} + +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 +} + +struct SignInWithApple_PreviewProvider: PreviewProvider { + static var previews: some View { + SignInWithAppleView() + } +} diff --git a/Sources/GoTrue/GoTrueClient.swift b/Sources/GoTrue/GoTrueClient.swift index 6acb115..e6f4faf 100644 --- a/Sources/GoTrue/GoTrueClient.swift +++ b/Sources/GoTrue/GoTrueClient.swift @@ -167,6 +167,7 @@ public final class GoTrueClient { /// Allows signing in with an ID token issued by certain supported providers. /// The ID token is verified for validity and a new session is established. + @_spi(Experimental) @discardableResult public func signInWithIdToken(credentials: OpenIDConnectCredentials) async throws -> Session { try await _signIn( diff --git a/Tests/GoTrueTests/GoTrueTests.swift b/Tests/GoTrueTests/GoTrueTests.swift index 0813470..884efa5 100644 --- a/Tests/GoTrueTests/GoTrueTests.swift +++ b/Tests/GoTrueTests/GoTrueTests.swift @@ -1,7 +1,7 @@ import Mocker import XCTest -@testable import GoTrue +@testable @_spi(Experimental) import GoTrue final class InMemoryLocalStorage: GoTrueLocalStorage, @unchecked Sendable { private let queue = DispatchQueue(label: "InMemoryLocalStorage")