simon-weber / gmusicapi

An unofficial client library for Google Music.
https://unofficial-google-music-api.readthedocs.io
BSD 3-Clause "New" or "Revised" License
2.48k stars 257 forks source link

Webclient requires additional steps to log in from new ip #475

Open JakedUp opened 8 years ago

JakedUp commented 8 years ago

The docs state "Users who don’t use two-factor auth will likely need to enable less secure login. If this is needed, a warning will be logged during login (which will print to stderr in the default logging configuration)."

However, I cannot get login to work. I've tried with 2-step & app-specific passwords, and I've tried without 2-step & less secure login enabled. Either way, when I login, it always returns false. When looking at the log file, I see the following entry: "gmusicapi.Webclient3 (webclient:61) [INFO]: failed to authenticate". Mobileclient and Musicmanager login just fine.

I know it's suggested to not use the Webclient, but as far as I can tell, it's the only way I can get my cover art uploaded correctly (Webclient.upload_album_art). When uploading music via Musicmanager, my cover art is removed from the MP3.

Is Webclient login no longer working?

JakedUp commented 8 years ago

Got it! Started running session.py line-by-line over SSH, and found that after submitting the password, there was a third screen where it was asking me to do one of 4 things:

I simulated submitting one of these steps over SSH, and then Webclient.login worked perfectly.

        form_candidates = response.soup.select("form")
        form = form_candidates[3]
        response = browser.submit(form, 'https://accounts.google.com/ServiceLoginAuth')
        form_candidates = response.soup.select("form")
        form = form_candidates[0]
        form.select('[name=email]')[0]['value'] = 'xxx@gmail.com'
        response = browser.submit(form, 'https://accounts.google.com/ServiceLoginAuth')

I suspect I needed to do this step since I'm running gmusicapi off of a remote webserver, and therefore Google was trying to block the login since it didn't appear to be a known IP address. This is just a theory, but it makes sense.

Wonder if this flow could somehow be integrated into the Webclient login?

simon-weber commented 8 years ago

Ah. Yeah, we've had trouble with webclient auth on remote machines: I actually turned off webclient testing from travis because of it.

I don't think there's an easy solution. The webclient just wasn't built for programmatic auth.

JakedUp commented 8 years ago

It's all good. Now that I've successfully logged in with the work-around above, that IP address is now known to Google, and I am longer having login issues. Hopefully this thread helps someone else who is having the same issue. You should probably also look into putting a notice in the docs about remote connections not working. I could not find a single thread of information about this anywhere.

askvictor commented 8 years ago

I had some problems with using a TFA account with WebClient - it won't accept an app-specific password, but it's not possible to use less-secure login with TFA. I added the following lines in session.py after submitting the password. I also had to change the login method in WebClient to accept kwargs so as to be able to pass the TFA token across; now I can log in using something like wc.login("email", "password", totp="TFATokenFromAuthenticator")

        if 'totp' in kwargs:  # Using 2FA
            form_candidates = response.soup.select("form")

            #commented out as there is another form to try a different option
            # if len(form_candidates) > 1:
            #     log.error("Google login form dom has changed; there are %s candidate forms:\n%s",
            #               len(form_candidates), form_candidates)
            #     return False

            form = form_candidates[0]
            form.select("#totpPin")[0]['value'] = kwargs["totp"]

            response = browser.submit(form, 'https://accounts.google.com/signin/challenge/totp/2')

            try:
                response.raise_for_status()
            except requests.HTTPError:
                log.exception("submitting OTP form failed")
                return False
sashgorokhov commented 7 years ago

Had the same issue. Just False login status, same messages in logs. Added application password in google and used it instead of mine. worked.