mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
remove appauth and use google sign in lib instead
This commit is contained in:
parent
321914dd80
commit
77081290e5
6 changed files with 149 additions and 131 deletions
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ struct InnerRootView: View {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
.onOpenURL { Authenticator.handleGoogleURL(url: $0) }
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ struct WelcomeView: View {
|
|||
|
||||
if AppKeys.sharedInstance?.iosClientGoogleId != nil {
|
||||
GoogleAuthButton {
|
||||
viewModel.handleGoogleAuth(authenticator: authenticator)
|
||||
Task {
|
||||
await viewModel.handleGoogleAuth(authenticator: authenticator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
Loading…
Reference in a new issue