remove appauth and use google sign in lib instead

This commit is contained in:
Satindar Dhillon
2022-06-20 23:43:23 -07:00
parent 321914dd80
commit 77081290e5
6 changed files with 149 additions and 131 deletions

View File

@ -33,7 +33,6 @@ let package = Package(
name: "Services",
dependencies: [
.product(name: "GoogleSignIn", package: "GoogleSignIn-iOS"),
.product(name: "AppAuth", package: "AppAuth-iOS"),
"Valet",
.product(name: "SwiftGraphQL", package: "swift-graphql"),
"Models",
@ -64,7 +63,6 @@ var appPackageDependencies: [Target.Dependency] {
var dependencies: [Package.Dependency] {
var deps: [Package.Dependency] = [
.package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.4.0")),
.package(url: "https://github.com/Square/Valet", from: "4.1.2"),
.package(url: "https://github.com/maticzav/swift-graphql", from: "2.3.1"),
.package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"),

View File

@ -66,21 +66,18 @@ final class RegistrationViewModel: ObservableObject {
.store(in: &subscriptions)
}
func handleGoogleAuth(authenticator: Authenticator) {
authenticator
.handleGoogleAuth(presentingViewController: presentingViewController())
.sink(
receiveCompletion: { [weak self] completion in
guard case let .failure(loginError) = completion else { return }
self?.loginError = loginError
},
receiveValue: { [weak self] isNewAccount in
if isNewAccount {
self?.registrationState = .createProfile(userProfile: UserProfile(username: "", name: ""))
}
}
)
.store(in: &subscriptions)
func handleGoogleAuth(authenticator: Authenticator) async {
guard let presentingViewController = presentingViewController() else { return }
let googleAuthResponse = await authenticator.handleGoogleAuth(presenting: presentingViewController)
switch googleAuthResponse {
case let .loginError(error):
loginError = error
case .newOmnivoreUser:
registrationState = .createProfile(userProfile: UserProfile(username: "", name: ""))
case .existingOmnivoreUser:
break
}
}
}

View File

@ -97,6 +97,7 @@ struct InnerRootView: View {
}
}
#endif
.onOpenURL { Authenticator.handleGoogleURL(url: $0) }
}
#if os(iOS)

View File

@ -139,7 +139,9 @@ struct WelcomeView: View {
if AppKeys.sharedInstance?.iosClientGoogleId != nil {
GoogleAuthButton {
viewModel.handleGoogleAuth(authenticator: authenticator)
Task {
await viewModel.handleGoogleAuth(authenticator: authenticator)
}
}
}
}

View File

@ -1,6 +1,6 @@
import AppAuth
import Combine
import Foundation
import GoogleSignIn
import Models
import Utils
import WebKit
@ -8,6 +8,10 @@ import WebKit
public final class Authenticator: ObservableObject {
public static var unregisterIntercomUser: (() -> Void)?
public static func handleGoogleURL(url: URL) {
GIDSignIn.sharedInstance.handle(url)
}
public enum AuthStatus {
case loggedOut
case pendingUser
@ -20,13 +24,8 @@ public final class Authenticator: ObservableObject {
let networker: Networker
var subscriptions = Set<AnyCancellable>()
var currentAuthorizationFlow: OIDExternalUserAgentSession?
var pendingUserToken: String?
#if os(macOS)
var authRedirectHandler: OIDRedirectHTTPHandler?
#endif
public init(networker: Networker) {
self.networker = networker
self.isLoggedIn = ValetKey.authToken.exists

View File

@ -1,123 +1,144 @@
import AppAuth
import Combine
import Foundation
import GoogleSignIn
import Models
import Utils
#if os(iOS)
import UIKit
public enum GoogleAuthResponse {
case loginError(error: LoginError)
case newOmnivoreUser
case existingOmnivoreUser
}
public extension Authenticator {
func handleGoogleAuth(presentingViewController: PlatformViewController?) -> AnyPublisher<Bool, LoginError> {
Future { [weak self] promise in
guard let self = self, let presenting = presentingViewController else { return }
// swiftlint:disable:next line_length
self.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: self.googleAuthRequest(redirectURL: nil), presenting: presenting) { authState, authError in
self.resolveAuthResponse(promise: promise, authState: authState, authError: authError)
}
}
.eraseToAnyPublisher()
}
extension Authenticator {
public func handleGoogleAuth(presenting: PlatformViewController) async -> GoogleAuthResponse {
let authToken = try? await googleSignIn(presenting: presenting)
guard let authToken = authToken else { return .loginError(error: .unauthorized) }
// TODO: sync with server
return .existingOmnivoreUser
}
#endif
#if os(macOS)
import AppKit
public extension Authenticator {
func handleGoogleAuth(presentingViewController _: PlatformViewController?) -> AnyPublisher<Bool, LoginError> {
authRedirectHandler = OIDRedirectHTTPHandler(
successURL: URL(string: "https://omnivore.app")!
)
let redirectURL = authRedirectHandler?.startHTTPListener(nil)
let authRequest = googleAuthRequest(redirectURL: redirectURL)
return Future { [weak self] promise in
guard let self = self else { return }
// swiftlint:disable:next line_length
self.authRedirectHandler?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: authRequest) { authState, authError in
NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
self.resolveAuthResponse(promise: promise, authState: authState, authError: authError)
func googleSignIn(presenting: PlatformViewController) async throws -> String {
try await withCheckedThrowingContinuation { continuation in
let clientID = "\(AppKeys.sharedInstance?.iosClientGoogleId ?? "").apps.googleusercontent.com"
GIDSignIn.sharedInstance.signIn(
with: GIDConfiguration(clientID: clientID),
presenting: presenting
) { user, error in
guard let user = user, error == nil else {
continuation.resume(throwing: LoginError.unauthorized)
return
}
}
.eraseToAnyPublisher()
}
}
#endif
private extension Authenticator {
func resolveAuthResponse(
promise: @escaping (Result<Bool, LoginError>) -> Void,
authState: OIDAuthState?,
authError: Error?
) {
if let idToken = authState?.lastTokenResponse?.idToken {
Task {
do {
let authPayload = try await networker.submitGoogleToken(idToken: idToken)
try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString)
try ValetKey.authToken.setValue(authPayload.authToken)
DispatchQueue.main.async {
self.isLoggedIn = true
}
} catch {
if let error = error as? LoginError {
switch error {
case .unauthorized, .unknown:
self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError)
case .network:
promise(.failure(error))
}
self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError)
user.authentication.do { authentication, error in
guard let idToken = authentication?.idToken, error == nil else {
continuation.resume(throwing: LoginError.unauthorized)
return
}
continuation.resume(returning: idToken)
}
}
} else {
resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError)
}
}
func resolveAuthResponseForAccountCreation(
promise: @escaping (Result<Bool, LoginError>) -> Void,
authState: OIDAuthState?,
authError _: Error?
) {
if let idToken = authState?.lastTokenResponse?.idToken {
let params = CreatePendingAccountParams(token: idToken, provider: .google, fullName: nil)
let encodedParams = (try? JSONEncoder().encode(params)) ?? Data()
networker
.createPendingUser(params: encodedParams)
.sink { completion in
guard case let .failure(serverError) = completion else { return }
promise(.failure(LoginError.make(serverError: serverError)))
} receiveValue: { [weak self] in
self?.pendingUserToken = $0.pendingUserToken
promise(.success(true))
}
.store(in: &subscriptions)
} else {
promise(.failure(.unauthorized))
}
}
func googleAuthRequest(redirectURL: URL?) -> OIDAuthorizationRequest {
let authEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")!
let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")!
let iosClientGoogleId = AppKeys.sharedInstance?.iosClientGoogleId ?? ""
let scopes = ["profile", "email"]
return OIDAuthorizationRequest(
configuration: OIDServiceConfiguration(authorizationEndpoint: authEndpoint, tokenEndpoint: tokenEndpoint),
clientId: "\(iosClientGoogleId).apps.googleusercontent.com",
scopes: scopes,
redirectURL: redirectURL ?? URL(
string: "com.googleusercontent.apps.\(iosClientGoogleId):/oauth2redirect/google"
)!,
responseType: "code",
additionalParameters: nil
)
}
}
// #if os(iOS)
// import UIKit
//
// public extension Authenticator {
// func handleGoogleAuthDep(presentingViewController: PlatformViewController?) -> AnyPublisher<Bool, LoginError> {
// Future { [weak self] promise in
// guard let self = self, let presenting = presentingViewController else { return }
//
// // swiftlint:disable:next line_length
// self.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: self.googleAuthRequest(redirectURL: nil), presenting: presenting) { authState, authError in
// self.resolveAuthResponse(promise: promise, authState: authState, authError: authError)
// }
// }
// .eraseToAnyPublisher()
// }
// }
// #endif
// #if os(macOS)
// import AppKit
//
// public extension Authenticator {
// func handleGoogleAuthDep(presentingViewController _: PlatformViewController?) -> AnyPublisher<Bool, LoginError> {
// authRedirectHandler = OIDRedirectHTTPHandler(
// successURL: URL(string: "https://omnivore.app")!
// )
// let redirectURL = authRedirectHandler?.startHTTPListener(nil)
// let authRequest = googleAuthRequest(redirectURL: redirectURL)
//
// return Future { [weak self] promise in
// guard let self = self else { return }
//
// // swiftlint:disable:next line_length
// self.authRedirectHandler?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: authRequest) { authState, authError in
// NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
// self.resolveAuthResponse(promise: promise, authState: authState, authError: authError)
// }
// }
// .eraseToAnyPublisher()
// }
// }
// #endif
// private extension Authenticator {
// func resolveAuthResponse(
// promise: @escaping (Result<Bool, LoginError>) -> Void,
// authState: OIDAuthState?,
// authError: Error?
// ) {
// if let idToken = authState?.lastTokenResponse?.idToken {
// Task {
// do {
// let authPayload = try await networker.submitGoogleToken(idToken: idToken)
// try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString)
// try ValetKey.authToken.setValue(authPayload.authToken)
// DispatchQueue.main.async {
// self.isLoggedIn = true
// }
// } catch {
// if let error = error as? LoginError {
// switch error {
// case .unauthorized, .unknown:
// self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError)
// case .network:
// promise(.failure(error))
// }
// self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError)
// }
// }
// }
// } else {
// resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError)
// }
// }
//
// func resolveAuthResponseForAccountCreation(
// promise: @escaping (Result<Bool, LoginError>) -> Void,
// authState: OIDAuthState?,
// authError _: Error?
// ) {
// if let idToken = authState?.lastTokenResponse?.idToken {
// let params = CreatePendingAccountParams(token: idToken, provider: .google, fullName: nil)
// let encodedParams = (try? JSONEncoder().encode(params)) ?? Data()
//
// networker
// .createPendingUser(params: encodedParams)
// .sink { completion in
// guard case let .failure(serverError) = completion else { return }
// promise(.failure(LoginError.make(serverError: serverError)))
// } receiveValue: { [weak self] in
// self?.pendingUserToken = $0.pendingUserToken
// promise(.success(true))
// }
// .store(in: &subscriptions)
// } else {
// promise(.failure(.unauthorized))
// }
// }
// }