diff --git a/wake.xcodeproj/project.pbxproj b/wake.xcodeproj/project.pbxproj index e45c27e..b8dfd9a 100644 --- a/wake.xcodeproj/project.pbxproj +++ b/wake.xcodeproj/project.pbxproj @@ -291,13 +291,15 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = wake/wake.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = GB3VPJ54BD; + DEVELOPMENT_TEAM = 392N3QB7XR; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = wake/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_NSUserTrackingUsageDescription = "我们需要访问您的Apple ID信息来为您提供登录服务"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -307,7 +309,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.memowake.wake; + PRODUCT_BUNDLE_IDENTIFIER = com.memowake.fairclip2025; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -320,13 +322,15 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = wake/wake.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = GB3VPJ54BD; + DEVELOPMENT_TEAM = 392N3QB7XR; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = wake/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_NSUserTrackingUsageDescription = "我们需要访问您的Apple ID信息来为您提供登录服务"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -336,7 +340,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.memowake.wake; + PRODUCT_BUNDLE_IDENTIFIER = com.memowake.fairclip2025; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate index 39fe2f5..0aac839 100644 Binary files a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate and b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/wake/Info.plist b/wake/Info.plist index 3a0d680..d4f0a89 100644 --- a/wake/Info.plist +++ b/wake/Info.plist @@ -2,13 +2,8 @@ - UIAppFonts - - Quicksand x.ttf - SankeiCutePopanime.ttf - - - + ASAuthorizationAppleIDProvider + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleURLTypes @@ -18,14 +13,17 @@ - - ASAuthorizationAppleIDProvider - $(PRODUCT_BUNDLE_IDENTIFIER) - LSApplicationQueriesSchemes appleid appleauth + NSAppleIDUsageDescription + Sign in with Apple is used to authenticate your account + UIAppFonts + + Quicksand x.ttf + SankeiCutePopanime.ttf + diff --git a/wake/View/Login/Login.swift b/wake/View/Login/Login.swift index d9d4dde..895447e 100644 --- a/wake/View/Login/Login.swift +++ b/wake/View/Login/Login.swift @@ -1,165 +1,290 @@ import SwiftUI import AuthenticationServices import Alamofire +import CryptoKit -struct Post: Codable { - let id: Int - let title: String - let body: String - let userId: Int -} - -struct Login: Encodable { - let account: String - let password: String -} - +/// Main login view that handles Apple Sign In struct LoginView: View { + // MARK: - Properties @Environment(\.dismiss) private var dismiss - @State private var showModal = false @State private var isLoading = false + @State private var showError = false + @State private var errorMessage = "" + @State private var currentNonce: String? + // MARK: - Body var body: some View { - NavigationView { - ZStack { - VStack { - // App logo or title - VStack { - Image(systemName: "person.circle.fill") - .resizable() - .frame(width: 80, height: 80) - .foregroundColor(.blue) - .padding(.bottom, 20) - - Text("Welcome") - .font(.largeTitle) - .fontWeight(.bold) - .padding(.bottom, 40) - } - - // Apple Sign In Button - SignInWithAppleButton( - onRequest: { request in - request.requestedScopes = [.fullName, .email] - }, - onCompletion: { result in - switch result { - case .success(let authResults): - print("Authorization successful: \(authResults)") - // Handle successful authentication - // You can get user details from authResults.credential - if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential { - let userId = appleIDCredential.user - let email = appleIDCredential.email - let fullName = appleIDCredential.fullName - - print("User ID: \(userId)") - print("Email: \(email ?? "No email")") - print("Name: \(fullName?.givenName ?? "") \(fullName?.familyName ?? "")") - - // TODO: Send this information to your backend - // For example: - // loginWithApple(userId: userId, email: email, name: "\(fullName?.givenName ?? "") \(fullName?.familyName ?? "")") - - // Dismiss the login view after successful login - DispatchQueue.main.async { - self.dismiss() - } - } - - case .failure(let error): - print("Authorization failed: \(error.localizedDescription)") - // Handle error - } - } - ) - .signInWithAppleButtonStyle(.black) // or .white, .whiteOutline - .frame(height: 50) - .padding(.horizontal, 40) - .padding(.bottom, 20) - - // Terms and Privacy Policy - VStack(spacing: 8) { - Text("By continuing, you agree to our") - .font(.caption) - .foregroundColor(.gray) - - HStack(spacing: 16) { - Button("Terms of Service") { - // Open terms URL - if let url = URL(string: "https://yourwebsite.com/terms") { - UIApplication.shared.open(url) - } - } - .font(.caption) - .foregroundColor(.blue) - - Text("•") - .foregroundColor(.gray) - - Button("Privacy Policy") { - // Open privacy policy URL - if let url = URL(string: "https://yourwebsite.com/privacy") { - UIApplication.shared.open(url) - } - } - .font(.caption) - .foregroundColor(.blue) - } - } - .padding(.bottom, 40) - - Spacer() - } - .padding() - .navigationBarHidden(true) - .navigationBarBackButtonHidden(true) - - // Loading indicator - if isLoading { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - - ProgressView() - .scaleEffect(1.5) - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - } + ZStack { + // Background + Color(.systemBackground) + .edgesIgnoringSafeArea(.all) + + // Main content + VStack(spacing: 24) { + Spacer() + appHeaderView() + signInButton() + Spacer() + termsAndPrivacyView() } + .padding() + .alert(isPresented: $showError) { + Alert( + title: Text("Error"), + message: Text(errorMessage), + dismissButton: .default(Text("OK")) + ) + } + + // Loading indicator + if isLoading { + loadingView() + } + } + .navigationBarHidden(true) + } + + // MARK: - View Components + + /// App header with icon and welcome text + private func appHeaderView() -> some View { + VStack(spacing: 16) { + Image(systemName: "person.circle.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 80, height: 80) + .foregroundColor(.blue) + + Text("Welcome to Wake") + .font(.largeTitle) + .fontWeight(.bold) + + Text("Sign in to continue") + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(.bottom, 40) + } + + /// Apple Sign In button + private func signInButton() -> some View { + SignInWithAppleButton( + onRequest: { request in + // Generate nonce for security + let nonce = String.randomURLSafeString(length: 32) + self.currentNonce = nonce + + // Configure the request + request.requestedScopes = [.fullName, .email] + request.nonce = self.sha256(nonce) + }, + onCompletion: handleAppleSignIn + ) + .signInWithAppleButtonStyle(.black) + .frame(height: 50) + .padding(.horizontal, 40) + .cornerRadius(10) + } + + /// Terms and Privacy policy links + private func termsAndPrivacyView() -> some View { + VStack(spacing: 8) { + Text("By continuing, you agree to our") + .font(.caption) + .foregroundColor(.secondary) + + HStack(spacing: 16) { + Button("Terms of Service") { + openURL("https://yourwebsite.com/terms") + } + .font(.caption) + .foregroundColor(.blue) + + Text("•") + .foregroundColor(.secondary) + + Button("Privacy Policy") { + openURL("https://yourwebsite.com/privacy") + } + .font(.caption) + .foregroundColor(.blue) + } + } + .padding(.bottom, 24) + } + + /// Loading overlay view + private func loadingView() -> some View { + return ZStack { + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + + ProgressView() + .scaleEffect(1.5) + .progressViewStyle(CircularProgressViewStyle(tint: .white)) } } - // Example function to handle login with your backend - private func loginWithApple(userId: String, email: String?, name: String) { + // MARK: - Apple Sign In Handlers + + /// Handles the result of Apple Sign In + private func handleAppleSignIn(result: Result) { + switch result { + case .success(let authResults): + processAppleIDCredential(authResults.credential) + case .failure(let error): + handleSignInError(error) + } + } + + /// Processes the Apple ID credential + private func processAppleIDCredential(_ credential: ASAuthorizationCredential) { + guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else { + showError(message: "Unable to process Apple ID credentials") + return + } + + // Get user data + let userId = appleIDCredential.user + let email = appleIDCredential.email ?? "" + let fullName = [ + appleIDCredential.fullName?.givenName, + appleIDCredential.fullName?.familyName + ] + .compactMap { $0 } + .joined(separator: " ") + + // Get the identity token + guard let identityTokenData = appleIDCredential.identityToken, + let identityToken = String(data: identityTokenData, encoding: .utf8) else { + showError(message: "Unable to fetch identity token") + return + } + + // Get the authorization code (optional) + var authCode: String? = nil + if let authCodeData = appleIDCredential.authorizationCode { + authCode = String(data: authCodeData, encoding: .utf8) + } + + // Proceed with backend authentication + authenticateWithBackend( + userId: userId, + email: email, + name: fullName, + identityToken: identityToken, + authCode: authCode + ) + } + + // MARK: - Network Operations + + /// Authenticates the user with the backend server + private func authenticateWithBackend( + userId: String, + email: String, + name: String, + identityToken: String, + authCode: String? + ) { isLoading = true - // Replace with your actual API endpoint - let url = "https://your-api-endpoint.com/auth/apple" - let parameters: [String: Any] = [ + let url = "https://your-api-endpoint.com/api/auth/apple" + var parameters: [String: Any] = [ "appleUserId": userId, - "email": email ?? "", + "email": email, "name": name, - // Add any other required parameters + "identityToken": identityToken ] + // Add authorization code if available + if let authCode = authCode { + parameters["authorizationCode"] = authCode + } + AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default) .validate() .responseJSON { response in - isLoading = false + self.isLoading = false switch response.result { case .success(let value): - print("Login successful: \(value)") - // Handle successful login (e.g., save auth token, navigate to home screen) - dismiss() - + print("Authentication successful: \(value)") + self.handleSuccessfulAuthentication() case .failure(let error): - print("Login failed: \(error.localizedDescription)") - // Show error message to user + self.handleAuthenticationError(error) } } } + + // MARK: - Helper Methods + + /// Handles successful authentication + private func handleSuccessfulAuthentication() { + DispatchQueue.main.async { + self.dismiss() + } + } + + /// Handles sign in errors + private func handleSignInError(_ error: Error) { + let errorMessage = (error as NSError).localizedDescription + print("Apple Sign In failed: \(errorMessage)") + showError(message: "Sign in failed: \(error.localizedDescription)") + } + + /// Handles authentication errors + private func handleAuthenticationError(_ error: AFError) { + let errorMessage = error.localizedDescription + print("API Error: \(errorMessage)") + showError(message: "Failed to sign in: \(errorMessage)") + } + + /// Shows error message to the user + private func showError(message: String) { + DispatchQueue.main.async { + self.errorMessage = message + self.showError = true + } + } + + /// Opens a URL in Safari + private func openURL(_ string: String) { + guard let url = URL(string: string) else { return } + UIApplication.shared.open(url) + } + + 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 + } } -#Preview { - LoginView() +// MARK: - String Extension for Random String Generation + +extension String { + /// Generates a random string of the specified length using URL-safe characters + /// - Parameter length: The length of the random string to generate + /// - Returns: A random string containing URL-safe characters + static func randomURLSafeString(length: Int) -> String { + let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" + var randomString = "" + + for _ in 0.. + + + + com.apple.developer.applesignin + + Default + + +