import Foundation import AuthenticationServices /// Google OAuth (via ASWebAuthenticationSession) — an alternative account /// identity path. Google is a true OAuth provider, unlike Instagram/X which /// are not suitable for login (see AuthService notes). final class GoogleAuthProvider: NSObject, AuthProvider { var providerID: AuthProviderID { .google } private let clientID = "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com" private let redirectURI = "com.proximity.app:/oauth2redirect" func signIn() async throws -> AuthResult { // Build the Google OAuth2 authorization URL. var components = URLComponents(string: "https://accounts.google.com/o/oauth2/v2/auth")! components.queryItems = [ URLQueryItem(name: "client_id", value: clientID), URLQueryItem(name: "redirect_uri", value: redirectURI), URLQueryItem(name: "response_type", value: "code"), URLQueryItem(name: "scope", value: "openid email profile"), URLQueryItem(name: "nonce", value: UUID().uuidString) ] guard let url = components.url else { throw AuthError.invalidCredential } // Present the ASWebAuthenticationSession and await the callback. let callbackURL = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in let session = ASWebAuthenticationSession( url: url, callbackURLScheme: "com.proximity.app" ) { callback, error in if let error { continuation.resume(throwing: error) } else if let callback { continuation.resume(returning: callback) } else { continuation.resume(throwing: AuthError.invalidCredential) } } session.presentationContextProvider = self session.start() } // Extract the authorization code from the callback. guard let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false), let code = components.queryItems?.first(where: { $0.name == "code" })?.value else { throw AuthError.invalidCredential } // In production: exchange `code` for an ID token at our backend, // which verifies it with Google. Here we pass the code through. return AuthResult( providerID: .google, idToken: code, nonce: nil, profile: ProviderProfile() ) } func signOut() async throws { // Revoke handled by backend. } } extension GoogleAuthProvider: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { ASPresentationAnchor() } }