p2 / OAuth2

OAuth2 framework for macOS and iOS, written in Swift.
Other
1.14k stars 275 forks source link

How to wait oauth callback for triggering handleRedirectURL #398

Open cgkronos opened 1 year ago

cgkronos commented 1 year ago

Now, I've another issue: I've those methods

func runOauth(){
        self.loadingLabel.isHidden=true
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.oauth2!.afterAuthorizeOrFail = self.callBackOAuth

        var url:URL?
        do{
            //the url for authorizing the user, kronos://oauth/callback" is called after the OAuth finish

            url = try appDelegate.oauth2!.authorizeURL(withRedirect:"kronos://oauth/callback", scope: "auth",params: ["tg":"addon/kronos/main","idx":"login.OAuth","formId":"iOS"])
            do{
                let authorizer = appDelegate.oauth2!.authorizer as! OAuth2Authorizer
                //launch OAuth in embeded view "SafariVC"
                print("Safari embeded" + url!.absoluteString)

                safariVC = try authorizer.authorizeSafariEmbedded(from: self,at: url!)

            }catch let error {
                DispatchQueue.main.async {
                    print("ERROR authorizing\(error)")
                    //self.runOauth()
                }
            }
        }catch let error {
            DispatchQueue.main.async {
                print("ERROR creating OAuth URL \(error)")
                //self.runOauth()
            }
        }
    }
func callBackOAuth(authParameters:OAuth2JSON!, error: OAuth2Error!){
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        print("Callback")
        if (error ==  nil && appDelegate.oauth2!.accessToken != nil){//OAuth succeed in

            //we store the token and its experation date in keychain
            print("OAuth succeed")
            self.keychain!.set(appDelegate.oauth2!.accessToken!,forKey:"Token")
            //self.keychain!.set(appDelegate.oauth2!.refreshToken!,forKey:"RefreshToken")
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            let myString = formatter.string(from: appDelegate.oauth2!.accessTokenExpiry!)
            self.keychain!.set(myString,forKey:"ExpiryDate")
            self.loadingLabel.isHidden=false

            appDelegate.reloadView()
        }else if (error !=  nil){//OAUth failed
            print("OAuth error \(String(describing: error))")
        }else{//Another error
            print("Cannot login")
            self.showMessage(msg: "Login error", title: "Error")
            self.runOauth()
        }
    }

and in AppDelegate

func application(_ app: UIApplication,
                  open url: URL,
                  options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        // you should probably first check if this is the callback being opened
            // if your oauth2 instance lives somewhere else, adapt accordingly
        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
        let site=components?.host
        print("Application")
        if site == "oauth"{//OAuth terminated
            if components?.path == "/callback" {
                let viewController = self.window?.rootViewController as! ViewController
                print("oauth")
                self.oauth2!.handleRedirectURL(url)
                viewController.hideSafariView()
            }
        }

        return true
    }

My issue is that as I trigger runOauth like that it happens that application is called before callBackOAuth so after oauth viewDidAppear is recalled but with keychain token not set, so here is a way to "wait" in application that token is not nil

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        //initialize OAuth2 config parameters
        appDelegate.oauth2 = OAuth2CodeGrant(settings: OAuthParams  )
        appDelegate.oauth2!.authConfig.authorizeContext = KronosWebsite?.window
        appDelegate.oauth2!.useKeychain = false
        appDelegate.oauth2!.authConfig.authorizeEmbeddedAutoDismiss = true

        appDelegate.oauth2!.logger = OAuth2DebugLogger(.debug)
        appDelegate.oauth2!.afterAuthorizeOrFail = self.callBackOAuth
        appDelegate.oauth2!.verbose = true

        //try to load the Token from keychain
        let token=self.keychain!.get("Token")

        if(token == nil){//no token found, we launch the OAuth
            print("no token yet")
            runOauth()
        } 

EDIT: I've tried to use a DispatchGroup with no success:

groupOauth.enter() in runOauth

groupOauth.leave() in callBackOAuth

and in AppDelegate::application

 viewController.groupOauth.notify(queue: DispatchQueue.main) {
    self.oauth2!.handleRedirectURL(url)
    viewController.hideSafariView()
}
cgkronos commented 1 year ago

I've a solution by deporting the call of the URL which logs the user (with the Oauth token stored in keychain) from viewDidAppear to viewDidLoad