etheralm / libpurecool

Python library for dyson devices.
Other
66 stars 30 forks source link

Invalid login since 11/01/2021 ? #37

Open qdel opened 3 years ago

qdel commented 3 years ago

Hi,

Since the 11/01/2021 morning, the login webservice return a 500 error with the html page: error preview.

The dyson link application seems to work. Credential are correct.

Note also that cloudfare seems to limit the number of request we can do on this webservice. After a couple of 500, we take an error 429 with a Retry-After header set to 3600. (if you want, i made a fix for this and can make a PR).

qdel commented 3 years ago

Update:

Since around 12:00 Paris time i now meet a 401/{"Message":"Unable to authenticate user."}.

But i can still connect with the same credential using dyson link application and dyson website.

Sadly i don't know how to trace trafic between my phone and their webservices.

googanhiem commented 3 years ago

Last time we had login issues with libpurecool they were caused by a lack of header info in the request, causing us to add a user agent (same as the app). It was assumed at the time this would eventually be blocked by dyson.

Tloram commented 3 years ago

Same issue here, as of today all 3 of my Dyson fans are unable to auth. Any ideas?

qdel commented 3 years ago

Maybe if someone can track how the dyson app connect to the server, we will have all infos.

I can try making a custom made webserver and rerouting the dns call of my phone. But can't it right now.

bfayers commented 3 years ago

All I can work out MITM-ing my phone is that it's using the linkapp-api.dyson.com domain

googanhiem commented 3 years ago

Also an FYI @shenxn is looking to build a new local control component to link Dyson to Home Assistant.

Not sure if they'd be able to shed any light on any changes to the Dyson API.

Another FYI, @etheralm has said he doesn't really have the time to work on this anymore, so its unlikely we'll be able to easily PR whatever change is needed for this issue.. probably needs a fork at this point.

bfayers commented 3 years ago

Header needs to be changed to android client and then it works again. Source: https://github.com/lukasroegner/homebridge-dyson-pure-cool/pull/153

I tested this via postman and got the account/password things in response.

googanhiem commented 3 years ago

Header needs to be changed to android client and then it works again.

Just tested this change to libpurecool in home assistant (by editing current header in the dyson.py) and it didn't work for me.

bfayers commented 3 years ago

Just tested this change to libpurecool in home assistant (by editing current header in the dyson.py) and it didn't work for me.

The same request I tried earlier in Postman now doesn't work. seems to be hit or miss as to whether it's going to work or not 🤔

Once this auth is worked out, it may be worth considering saving the credentials the API gives in the hass integration - depending on how long they last ofc.

bfayers commented 3 years ago

@googanhiem Ah, interesting the android client header works but only if you log out of the app, then log back in and the endpoint will work with that header for.... an amount of time or number of requests that I don't know yet.

I guess this could be related to the app talking to linkapp-api.dyson.com at some point during it's auth process.

EDIT: after double checking it looks like re-authing the app then using the header already in the library also works.

Something has to be screwy though, I can't get the library to auth even if Postman can.

googanhiem commented 3 years ago

Yeah, I can't replicate the behaviour you're talking about, too bad it would be a decent temp fix.

Maybe some of the auth requests are making it through cloudflare at the moment... so if you reboot and it works.. hold off rebooting for a while if you can.

bfayers commented 3 years ago

Yeah, something is really odd - I've got the Account and Password auths from Postman luckily - I can manually make the library work with testing like this:

dc = DysonAccount("","","")
dc._logged = True
dc._auth = HTTPBasicAuth("accountresponsefrompostman","passwordresponsefrompostman")

devs = dc.devices()
connected = devs[0].connect("ipaddress")
devs[0].turn_on()

which does result in the fan turning on.

I was able to achieve HASS functionality again by editing /usr/src/homeassistant/homeassistant/components/dyson/__init__.py

adding an import of from requests.auth import HTTPBasicAuth and below the call for logged = dyson_account.login() adding in:

    logged = True
    dyson_account._logged = True
    dyson_account._auth = HTTPBasicAuth("account", "password")

Of course, I've no idea how long those values last.

And just to clarify, it seems to be repeatable that using Postman to POST https://appapi.cp.dyson.com/v1/userregistration/authenticate?country=GB with appropriate JSON data always results in the Account and Password values if done immediately after re-authenticating the official dyson app - even with a user agent of DysonLink/29019 CFNetwork/1188 Darwin/20.0.0

Exporting the postman request as code is this:

import requests

url = "https://appapi.cp.dyson.com/v1/userregistration/authenticate?country=GB"

payload="{\r\n    \"Email\": \"emailhere\",\r\n    \"Password\": \"passwordhere\"\r\n}"
headers = {
  'User-Agent': 'DysonLink/29019 CFNetwork/1188 Darwin/20.0.0',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

And this does actually work - providing you've just re-authenticated the official app.

I also think it's worth mentioning that both those values have not changed during all my testing today.

shenxn commented 3 years ago

To properly send JSON data, you can use

response = requests.request("POST", url, headers=headers, json=payload)

instead of manually add Content-Type header. Note payload should be a dictionary instead of string.

shenxn commented 3 years ago

I think I found the problem. To make the authentication work, we should first check account status by making a GET request to /v1/userregistration/userstatus?country=GB&email=YOUR_EMAIL_ADDRESS and then do the normal login.

Grizzelbee commented 3 years ago

@shenxn Just tried your recommendation in my ioBroker-Adapter and can confirm: it's working.

bfayers commented 3 years ago

instead of manually add Content-Type header. Note payload should be a dictionary instead of string.

Ah yeah, my bad - that's just the code that postman generated.

Alexwijn commented 3 years ago

So in summary we only need to add this piece of code before we do the login.

        requests.get("https://{0}/v1/userregistration/userstatus?country={1}&email={2}".format(
            self._dyson_api_url, self._country, self._email
        ),
            headers=self._headers,
            verify=False
        )
shenxn commented 3 years ago

So in summary we only need to add this piece of code before we do the login.

        requests.get("https://{0}/v1/userregistration/userstatus?country={1}&email={2}".format(
            self._dyson_api_url, self._country, self._email
        ),
            headers=self._headers,
            verify=False
        )

You can use the params arg instead of string format so that requests can handle URL encode for you.

Alexwijn commented 3 years ago

You can use the params arg instead of string format so that requests can handle URL encode for you.

That is also an option, I just thought I would match the current code style.

abshgrp commented 3 years ago

Hey @shenxn - apologies I am a tad confused. Are you suggesting the only change required is to add adding the following before login within the dyson.py file? The changes from @bfayers are not required?

requests.get("https://{0}/v1/userregistration/userstatus?country={1}&email={2}".format( self._dyson_api_url, self._country, self._email ), headers=self._headers, verify=False )

I have tried that but still not working so just curious if @bfayers changes are also requried and your suggestion gets around having to auth using the phone app before login from HA?

class DysonAccount: """Dyson account."""

def __init__(self, email, password, country):
    """Create a new Dyson account.

    :param email: User email
    :param password: User password
    :param country: 2 characters language code
    """
    self._email = email
    self._password = password
    self._country = country
    self._logged = False
    self._auth = None
    self._headers = {'User-Agent': DYSON_API_USER_AGENT}
    if country == "CN":
        self._dyson_api_url = DYSON_API_URL_CN
    else:
        self._dyson_api_url = DYSON_API_URL

def login(self):
    **"""Check Dyson Account Status"""
    requests.get("https://{0}/v1/userregistration/userstatus?country={1}&email={2}".format(
        self._dyson_api_url, self._country, self._email
    ),
        headers=self._headers,
        verify=False
    )**

    """Login to dyson web services."""
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    _LOGGER.debug("Disabling insecure request warnings since "
                  "dyson are using a self signed certificate.")

    request_body = {
        "Email": self._email,
        "Password": self._password
    }
    login = requests.post(
        "https://{0}/v1/userregistration/authenticate?country={1}".format(
            self._dyson_api_url, self._country),
        headers=self._headers,
        data=request_body,
        verify=False
    )
bmorris591 commented 3 years ago

Based on this thread I've added exactly that to my Kotlin integration with Dyson and it seems to work.

The only difference is that I parse the result of the userstatus call to check the account is ACTIVE.

abshgrp commented 3 years ago

I have tried just @bfayers changes but I keep getting the following error. What am I doing wrong here?

Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/setup.py", line 213, in _async_setup_component result = await task File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run result = self.fn(*self.args, self.kwargs) File "/usr/src/homeassistant/homeassistant/components/dyson/init.py", line 68, in setup dyson_devices = dyson_account.devices() File "/usr/local/lib/python3.8/site-packages/libpurecool/dyson.py", line 93, in devices for device in device_response.json(): File "/usr/local/lib/python3.8/site-packages/requests/models.py", line 900, in json return complexjson.loads(self.text, kwargs) File "/usr/local/lib/python3.8/site-packages/simplejson/init.py", line 525, in loads return _default_decoder.decode(s) File "/usr/local/lib/python3.8/site-packages/simplejson/decoder.py", line 370, in decode obj, end = self.raw_decode(s) File "/usr/local/lib/python3.8/site-packages/simplejson/decoder.py", line 400, in raw_decode return self.scan_once(s, idx=_w(s, idx).end()) simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

image

Alexwijn commented 3 years ago

Hmm...I got it working now but I also needed to change the login logic to this:

        login = requests.post(
            "https://{0}/v1/userregistration/authenticate?country={1}".format(
                self._dyson_api_url, self._country),
            headers=self._headers,
            json=request_body,
            verify=False
        )

Notice I changed data to json.

abshgrp commented 3 years ago

Hey @Alexwijn did you only change the dyson.py file not the init.py?

googanhiem commented 3 years ago

@bfayers code works great. Thanks all

abshgrp commented 3 years ago

Thanks @bfayers! Updated dyson.py as per your commit and restored my init.py back to original and Dyson integration is now working again.

For anyone else that is confused and possibly not as technically proficient like myself, you only need to update the dyson.py file located /usr/local/lib/python3.8/site-packages/libpurecool. Refer to merge request above. Cheers

qdel commented 3 years ago

@bfayers just saw your PR, made an answer on https://github.com/home-assistant/core/issues/46400#issuecomment-778177969 but it was not the correct place to.

Find on my code that i also manage a http error 429.

bfayers commented 3 years ago

Find on my code that i also manage a http error 429.

@qdel Yeah - if you try to talk to the authentication API too much you'll get 429'd and have to wait an hour before trying again

Thanks @bfayers! Updated dyson.py as per your commit and restored my init.py back to original and Dyson integration is now working again.

@abshgrp Great to hear!

crowbarz commented 3 years ago

38 fixes it for me too, thanks @bfayers, @shenxn and others that helped figure out the solution! In case this helps others, rather than patch by hand I upgrade-installed the updated branch straight from git into my HA Supervised install before restarting: docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth

kritin81 commented 3 years ago

@crowbarz Thanks for your help, now my Dyson is working again.

38 fixes it for me too, thanks @bfayers, @shenxn and others that helped figure out the solution! In case this helps others, rather than patch by hand I upgrade-installed the updated branch straight from git into my HA Supervised install before restarting: docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth

Thanks for your help, Now my Dyson is working again.

slyoldfox commented 3 years ago

Also want to confirm that #38 works after modifying it locally in venv, thanks everyone ❤️

RonnyWinkler commented 3 years ago

For anyone else that is confused and possibly not as technically proficient like myself, you only need to update the dyson.py file located /usr/local/lib/python3.8/site-packages/libpurecool.

Is it possible to fix it in a Docker environment? Perhaps as Dyson-Integration as custom component? But what about the dyson.py locates in the Container, not in the config path?

curt7000 commented 3 years ago

Trying to fix this. I'm running HA in a VENV, Python3.8, but cannot find the dyson.py file in /usr/local/lib/python3.8/site-packages/libpurecool. Anyone have an idea where the dyson.py file would be located?

curt7000 commented 3 years ago

Trying to fix this. I'm running HA in a VENV, Python3.8, but cannot find the dyson.py file in /usr/local/lib/python3.8/site-packages/libpurecool. Anyone have an idea where the dyson.py file would be located?

Found it in /srv/homeassistant/lib/python3.8/site-packages/libpurecool/dyson.py , replaced it with https://raw.githubusercontent.com/bfayers/libpurecool/auth_customdeps/libpurecool/dyson.py

But it doesn't appear to be working. Any thoughts?

derme302 commented 3 years ago

38 Just tried it with HA in Docker and works great. (you might need to stop/start the container)

oneseventhree commented 3 years ago

@crowbarz Thanks for your help, now my Dyson is working again.

38 fixes it for me too, thanks @bfayers, @shenxn and others that helped figure out the solution! In case this helps others, rather than patch by hand I upgrade-installed the updated branch straight from git into my HA Supervised install before restarting: docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth

Thanks for your help, Now my Dyson is working again.

Where do you execute "docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth" ??

I can't find it in the UI or in Portainer?

kritin81 commented 3 years ago

@crowbarz Thanks for your help, now my Dyson is working again.

38 fixes it for me too, thanks @bfayers, @shenxn and others that helped figure out the solution! In case this helps others, rather than patch by hand I upgrade-installed the updated branch straight from git into my HA Supervised install before restarting: docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth

Thanks for your help, Now my Dyson is working again.

Where do you execute "docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth" ??

I can't find it in the UI or in Portainer?

You can follow this comment. https://github.com/home-assistant/core/issues/46400#issuecomment-778508999

pix3lize commented 3 years ago

38 fixes it for me too, thanks @bfayers, @shenxn and others that helped figure out the solution! In case this helps others, rather than patch by hand I upgrade-installed the updated branch straight from git into my HA Supervised install before restarting: docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth

Good job @crowbarz ! Thanks to you, now I know that home assistant is running on docker :)
Thanks @bfayers and @shenxn

To run the docker exec on the terminal.

Confirm it works after running the docker command

crowbarz commented 3 years ago

The command that you run and where you run it depends on which of the installation methods you used for your install:

qdel commented 3 years ago

To everybody: i think the discussion about home-assistant is of no interest in this discussion.

The final word is: the fix is there https://github.com/graham33/nur-packages/commit/1109c74e2a5b1168ba7b66038c317c0b9776b9fa We can consider the issue as closed. But "we" (the community) is not able to release a version without @etheralm.

Another FYI, @etheralm has said he doesn't really have the time to work on this anymore, so its unlikely we'll be able to easily PR whatever change is needed for this issue.. probably needs a fork at this point.

I don't know if he is ok for someone else to take the lead. And in this case, who?

googanhiem commented 3 years ago

Yeah, don't think @etheralm will care if someone takes over on this, its a fork to begin with, and on top of that he's removed himself as codeowner on HA in this edit.

Frankly with the state of @shenxn ha-dyson integration, I imagine very soon we won't need this dependency anyways. So yes, someone take the reigns, preferably someone who can make the necessary HA bump.

Unless we get lucky and @etheralm jumps in and quickly gives the PR his blessing.

qdel commented 3 years ago

@googanhiem : this project is still usefull for people not using HA but plain python.

ghost commented 3 years ago

@qdel as I understand it ha-dyson is built on libdyson which is a reworking of this library to achieve a number of goals

Having looked at the code, it does look very promising.

googanhiem commented 3 years ago

@googanhiem : this project is still useful for people not using HA but plain python.

Apologies, very true.

bfayers commented 3 years ago

Yeah, don't think @etheralm will care if someone takes over on this, its a fork to begin with, and on top of that he's removed himself as codeowner on HA in this edit.

Frankly with the state of @shenxn ha-dyson integration, I imagine very soon we won't need this dependency anyways. So yes, someone take the reigns, preferably someone who can make the necessary HA bump.

Unless we get lucky and @etheralm jumps in and quickly gives the PR his blessing.

In that case, I may look into pushing my fork to pypi and opening a PR in HASS to switch over.

Tloram commented 3 years ago

This appears to be failing again as of today.

bfayers commented 3 years ago

the accountstatus endpoint appears to still work - just the authenticate one is not working :/

I would give it a few hours before declaring it totally "broken" again - the Dyson API has in the past not worked and then worked later in the day.

ripburger commented 3 years ago

Could it have to do with Dyson adding 2FA? I am having problems again too as of yesterday :(

googanhiem commented 3 years ago

The Dyson app just got a major update yesterday which this might be tied to. I haven't been asked to add 2FA to my Dyson account yet... and haven't seen any updates on them adding that.

Mine hasn't stopped working yet, but I assume that's because I haven't rebooted.

jerryzou commented 3 years ago

the accountstatus endpoint appears to still work - just the authenticate one is not working :/

I would give it a few hours before declaring it totally "broken" again - the Dyson API has in the past not worked and then worked later in the day.

As my test went, if I sign in the official app first, the authenticate end point works as before (tested with postman). The accountstatus works as before anyway. But i seems that it is not used as pre-step anymore... (sorry i don't have necessary tools and knowledge to sniff what happens exactly with the updated dyson app when logging in)

So for now as a workround, if the HA has to restart, just sign in dyson official app before restart the HA.