Pyhass / Pyhiveapi

A python library to interface with the hive home api
MIT License
24 stars 18 forks source link

Issue with refreshtoken #63

Open pdev77 opened 1 year ago

pdev77 commented 1 year ago

Hi I'm simply using pyhiveapi to pull data from hive periodically. It has worked fine for months - but now they have introduced mandatory 2fa - I would have to 2fa at every data pull once the session["IdToken"] expires. This I guess is what the session["RefreshToken"] is for - but I cant figure how to use it. I've tried newTokens = hive_auth.refresh_token(authData['AuthenticationResult']['IdToken']) but it throws botocore.errorfactory.NotAuthorizedException: An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Invalid Refresh Token.

I'm definitely passing the right token - but if anyone can tell what is a miss it would be appreciated!

full sample code


import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import json
import sys
import argparse
import pyhiveapi as Hive
import time

requests.packages.urllib3.disable_warnings()

tokens = {}
hive_auth = Hive.Auth(args.username, args.password)

authData = hive_auth.login()

if authData.get("ChallengeName") == "SMS_MFA":
    code = input("Enter your 2FA code: ")
    authData = hive_auth.sms_2fa(code, authData)

if "AuthenticationResult" in authData:
    session = authData["AuthenticationResult"]
    tokens.update({"token": session["IdToken"]})
    tokens.update({"refreshToken": session["RefreshToken"]})
    tokens.update({"accessToken": session["AccessToken"]})

time.sleep(10)

newTokens = hive_auth.refresh_token(authData['AuthenticationResult']['RefreshToken'])

if "AuthenticationResult" in newTokens:
    session = newTokens["AuthenticationResult"]
    tokens.update({"token": session["IdToken"]})
    tokens.update({"refreshToken": session["RefreshToken"]})
    tokens.update({"accessToken": session["AccessToken"]})

print (tokens["token"])
print (tokens["refreshToken"])

exit()
imilne commented 1 year ago

I'm not a python programmer but I've been doing the same - a simple script that was running every 5 mins to grab the data (logging in every time) - but that's now borked with the 2FA. I'm assuming there must be some way that we can save the session info to a file, then reuse that on the next run?

My code was even simpler:

from pyhiveapi import Hive, SMS_REQUIRED

session = Hive(username="username", password="password")
login = session.login()

if login.get("ChallengeName") == SMS_REQUIRED:
    code = input("Enter 2FA code: ")
    session.sms_2fa(code, login)

session.startSession()

HeatingDevices = session.deviceList["climate"]

if len(HeatingDevices) >= 1:
    HeatingZone_1 = HeatingDevices[0]
    print("temperature=" + str(session.heating.getCurrentTemperature(HeatingZone_1)))
    print("targetTemperature=" + str(session.heating.getTargetTemperature(HeatingZone_1)))
    print("state=" + str(session.heating.getCurrentOperation(HeatingZone_1)))
pdev77 commented 1 year ago

Using the examples posted here: (https://pyhass.github.io/pyhiveapi.docs/docs/examples/session/) I've managed to get it all working again - using device authentication.

First - see https://github.com/Pyhass/Pyhiveapi/issues/57 - you will need to apply the fix to session.py.

Then - onetime only - you need to do the step "Log in - Using Hive Username and Password with MFA(if required)" to get the deviceKey/Password etc - then save them to a file / use them in the subsequent gather scripts.

For the data gather loop , then you need to follow steps "Log in - Using Hive Device Authentication" & start the session. You can then utilise the session object to extract data. No need to relogon with username/password each time - the deviceKey etc doesnt expire.

imilne commented 1 year ago

Yep, that's helped me sort it too 👍

supersausage85 commented 1 year ago

Using the examples posted here: (https://pyhass.github.io/pyhiveapi.docs/docs/examples/session/) I've managed to get it all working again - using device authentication.

First - see #57 - you will need to apply the fix to session.py.

Then - onetime only - you need to do the step "Log in - Using Hive Username and Password with MFA(if required)" to get the deviceKey/Password etc - then save them to a file / use them in the subsequent gather scripts.

For the data gather loop , then you need to follow steps "Log in - Using Hive Device Authentication" & start the session. You can then utilise the session object to extract data. No need to relogon with username/password each time - the deviceKey etc doesnt expire.

sorry for being a little dumb i am trying to do what you have successfully done

but for me the session login with with MFA just throws back erros after putting the 2FA code in it

imilne commented 1 year ago

Ok, my code is now split into two programs. You'd run this one first (and thankfully only once) to get a device authenticated. You'll need to punch in whatever 2FA code they text you:

from pyhiveapi import Hive, SMS_REQUIRED

session = Hive(username="username", password="password")

login = session.login()
if login.get("ChallengeName") == SMS_REQUIRED:
    code = input("Enter 2FA code: ")
    session.sms2fa(code, login)

 Device data is need for future device logins
session.auth.device_registration('pi4')
deviceData = session.auth.get_device_data()
print(deviceData)

That'll print out a bunch of tokens. You then need to feed them into something like the following, where the same tokens can be reused over and over:

from pyhiveapi import Hive, SMS_REQUIRED

session = Hive(username="username", password="password")

DEVICE_REQUIRED = "DEVICE_SRP_AUTH"
# This time tell the auth object about the device:
session.auth.device_group_key = 'FIRST_TOKEN'
session.auth.device_key = 'SECOND_TOKEN'
session.auth.device_password = 'THIRD_TOKEN'
# Now login - should be challenged with device login
login = session.login()
if login.get("ChallengeName") == DEVICE_REQUIRED:
    session.deviceLogin()
else:
    print("Are you sure device is registered?")

session.startSession()

HeatingDevices = session.deviceList["climate"]

if len(HeatingDevices) >= 1:
    HeatingZone_1 = HeatingDevices[0]
    print("temperature=" + str(session.heating.getCurrentTemperature(HeatingZone_1)))
    print("targetTemperature=" + str(session.heating.getTargetTemperature(HeatingZone_1)))
    print("state=" + str(session.heating.getCurrentOperation(HeatingZone_1)))

However, note that you do still have to make the edits to session.py that were mentioned earlier.

supersausage85 commented 1 year ago

edited please see below comment

supersausage85 commented 1 year ago

Ok, my code is now split into two programs. You'd run this one first (and thankfully only once) to get a device authenticated. You'll need to punch in whatever 2FA code they text you:

from pyhiveapi import Hive, SMS_REQUIRED

session = Hive(username="username", password="password")

login = session.login()
if login.get("ChallengeName") == SMS_REQUIRED:
    code = input("Enter 2FA code: ")
    session.sms2fa(code, login)

 Device data is need for future device logins
session.auth.device_registration('pi4')
deviceData = session.auth.get_device_data()
print(deviceData)

That'll print out a bunch of tokens. You then need to feed them into something like the following, where the same tokens can be reused over and over:

from pyhiveapi import Hive, SMS_REQUIRED

session = Hive(username="username", password="password")

DEVICE_REQUIRED = "DEVICE_SRP_AUTH"
# This time tell the auth object about the device:
session.auth.device_group_key = 'FIRST_TOKEN'
session.auth.device_key = 'SECOND_TOKEN'
session.auth.device_password = 'THIRD_TOKEN'
# Now login - should be challenged with device login
login = session.login()
if login.get("ChallengeName") == DEVICE_REQUIRED:
    session.deviceLogin()
else:
    print("Are you sure device is registered?")

session.startSession()

HeatingDevices = session.deviceList["climate"]

if len(HeatingDevices) >= 1:
    HeatingZone_1 = HeatingDevices[0]
    print("temperature=" + str(session.heating.getCurrentTemperature(HeatingZone_1)))
    print("targetTemperature=" + str(session.heating.getTargetTemperature(HeatingZone_1)))
    print("state=" + str(session.heating.getCurrentOperation(HeatingZone_1)))

However, note that you do still have to make the edits to session.py that were mentioned earlier.

ok so the first part has worked great now and registered the device i do however have another question now

from the second part your example mentions this

DEVICE_REQUIRED = "DEVICE_SRP_AUTH"

from where would this information be gathered please

thanks

imilne commented 1 year ago

You can leave that as is. It's literally what the code is, rather than anything that needs to be replaced.

supersausage85 commented 1 year ago

You can leave that as is. It's literally what the code is, rather than anything that needs to be replaced.

i just realised that

i have one final question please if you dont mind

with the second part you posted it outputs the temp which is great and proof of working concept

my project relies on me transfering the IdToken to php to interact with the rest of the project is there a way using the examples about i can vies the IdTokens so i can save them to file

or will this not work

thank you again for your help

imilne commented 1 year ago

Sorry, but I'm fairly clueless with python (Java is my day job). Even hacking this into a working state tested the limits of my python coding "skills".

supersausage85 commented 1 year ago

Sorry, but I'm fairly clueless with python (Java is my day job). Even hacking this into a working state tested the limits of my python coding "skills".

haha no problem i am useless with python too

all i need it to do now is print me a refresh token as in you example the token isnt ever seen

but i am also useless with python and also java

thanks

rjp commented 1 year ago

I think I've fixed this by following some AWS docs about how to refresh tokens with devices - I've been collecting temperature data every 5 minutes for 26 hours now with only one 2FA request at the start. Not quite as handy as "only one 2FA ever" if you do the device login dance but still nice to have it working! Will clean up my patches and submit a PR.