Open qdel opened 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.
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.
Same issue here, as of today all 3 of my Dyson fans are unable to auth. Any ideas?
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.
All I can work out MITM-ing my phone is that it's using the linkapp-api.dyson.com
domain
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.
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.
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.
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.
@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.
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.
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.
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.
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.
@shenxn Just tried your recommendation in my ioBroker-Adapter and can confirm: it's working.
instead of manually add
Content-Type
header. Notepayload
should be a dictionary instead of string.
Ah yeah, my bad - that's just the code that postman generated.
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
)
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.
You can use the
params
arg instead of string format so thatrequests
can handle URL encode for you.
That is also an option, I just thought I would match the current code style.
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
)
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
.
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)
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
.
Hey @Alexwijn did you only change the dyson.py file not the init.py?
@bfayers code works great. Thanks all
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
@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.
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!
docker exec -t homeassistant pip3 install --upgrade git+https://github.com/bfayers/libpurecool.git@fix_auth
@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.
Also want to confirm that #38 works after modifying it locally in venv, thanks everyone ❤️
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?
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?
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?
@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?
@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
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
The command that you run and where you run it depends on which of the installation methods you used for your install:
docker exec
command should be run from an ssh session to the installation host.pip
command, using pip
from the virtual environment that you installed HA Core into.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?
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.
@googanhiem : this project is still usefull for people not using HA but plain python.
@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 : this project is still useful for people not using HA but plain python.
Apologies, very true.
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.
This appears to be failing again as of today.
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.
Could it have to do with Dyson adding 2FA? I am having problems again too as of yesterday :(
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.
the
accountstatus
endpoint appears to still work - just theauthenticate
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.
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).