@@ -9,12 +9,33 @@ import Foundation
99import Core
1010import SwiftUI
1111import Alamofire
12+ import OAuthSwift
13+ import SafariServices
14+
15+ private class WebLoginSafariDelegate : NSObject , SFSafariViewControllerDelegate {
16+ private let viewModel : SignInViewModel
17+ public init ( viewModel: SignInViewModel ) {
18+ self . viewModel = viewModel
19+ }
20+ func safariViewControllerDidFinish( _ controller: SFSafariViewController ) {
21+ /* Called when the 'Done' button is hit on the Safari Web view. In this case,
22+ authentication would neither have failed nor succeeded, but we'd be back
23+ at the SignInView. So, we make sure we mark it as attempted so the UI
24+ renders. */
25+ self . viewModel. markAttempted ( )
26+ }
27+ }
1228
1329public class SignInViewModel : ObservableObject {
1430
1531 @Published private( set) var isShowProgress = false
1632 @Published private( set) var showError : Bool = false
1733 @Published private( set) var showAlert : Bool = false
34+ @Published private( set) var webLoginAttempted : Bool = false
35+
36+ var forceWebLogin : Bool {
37+ return config. webLogin && !webLoginAttempted
38+ }
1839 var errorMessage : String ? {
1940 didSet {
2041 withAnimation {
@@ -29,20 +50,78 @@ public class SignInViewModel: ObservableObject {
2950 }
3051 }
3152 }
53+ var oauthswift : OAuth2Swift ?
3254
3355 private let interactor : AuthInteractorProtocol
3456 let router : AuthorizationRouter
57+ let config : Config
3558 let analytics : AuthorizationAnalytics
3659 private let validator : Validator
60+ private var safariDelegate : WebLoginSafariDelegate ?
3761
3862 public init ( interactor: AuthInteractorProtocol ,
3963 router: AuthorizationRouter ,
4064 analytics: AuthorizationAnalytics ,
65+ config: Config ,
4166 validator: Validator ) {
4267 self . interactor = interactor
4368 self . router = router
4469 self . analytics = analytics
70+ self . config = config
4571 self . validator = validator
72+ self . webLoginAttempted = false
73+ }
74+
75+ @MainActor
76+ func login( viewController: UIViewController ) async {
77+ /* OAuth web login. Used when we cannot use the built-in login form,
78+ but need to let the LMS redirect us to the authentication provider.
79+
80+ An example service where this is needed is something like Auth0, which
81+ redirects from the LMS to its own login page. That login page then redirects
82+ back to the LMS for the issuance of a token that can be used for making
83+ requests to the LMS, and then back to the redirect URL for the app. */
84+ self . safariDelegate = WebLoginSafariDelegate ( viewModel: self )
85+ oauthswift = OAuth2Swift (
86+ consumerKey: config. oAuthClientId,
87+ consumerSecret: " " , // No secret required
88+ authorizeUrl: " \( config. baseURL) /oauth2/authorize/ " ,
89+ accessTokenUrl: " \( config. baseURL) /oauth2/access_token/ " ,
90+ responseType: " code "
91+ )
92+
93+ oauthswift!. allowMissingStateCheck = true
94+ let handler = SafariURLHandler (
95+ viewController: viewController, oauthSwift: oauthswift!
96+ )
97+ handler. delegate = self . safariDelegate
98+ oauthswift!. authorizeURLHandler = handler
99+
100+ // Trigger OAuth2 dance
101+ guard let rwURL = URL ( string: " \( Bundle . main. bundleIdentifier ?? " " ) ://oauth2Callback " ) else { return }
102+ oauthswift!. authorize ( withCallbackURL: rwURL, scope: " " , state: " " ) { result in
103+ switch result {
104+ case . success( let ( credential, _, _) ) :
105+ Task {
106+ self . webLoginAttempted = true
107+ let user = try await self . interactor. login ( credential: credential)
108+ self . analytics. setUserID ( " \( user. id) " )
109+ self . analytics. userLogin ( method: . oauth2)
110+ self . router. showMainScreen ( )
111+ }
112+ // Do your request
113+ case . failure( let error) :
114+ self . webLoginAttempted = true
115+ self . isShowProgress = false
116+ self . errorMessage = error. localizedDescription
117+ }
118+ }
119+ }
120+
121+ public func markAttempted( ) {
122+ // Hack to get around published observables limitation when handing this model over
123+ // to an outside object. Is there a better way to do this?
124+ self . webLoginAttempted = true
46125 }
47126
48127 @MainActor
0 commit comments