Pyhiveapi icon indicating copy to clipboard operation
Pyhiveapi copied to clipboard

Auth & Docs

Open JonEllis opened this issue 3 years ago • 3 comments

Hi all, I'm half expecting this to be my misunderstanding, but I'm having problems getting authorised to the point of getting access to my account data.

I am able to authenticate using my credentials, and enter my 2FA code, but have not been able to keep access when my original token expires.

I can register a trusted device with this, and it shows up in my account in the app:


auth = Hive.Auth(username, password)
authData = auth.login()
if authData.get("ChallengeName") == "SMS_MFA":
    code = input("Enter your 2FA code: ")
    authData = auth.sms_2fa(code, authData).get('AuthenticationResult') # assumes all was OK

auth.device_registration('MyTrustedDevice')

accessToken = authData['AccessToken']
refreshToken = authData['RefreshToken']
idToken = authData['IdToken']

deviceKey = authData['NewDeviceMetadata']['DeviceKey']
deviceGroupKey = authData['NewDeviceMetadata']['DeviceGroupKey']
devicePassword = auth.device_password

Then I am able to authenticate again using these pieces of information, and have the returned tokens:


auth = Hive.Auth(username, password, deviceGroupKey, deviceKey, devicePassword)
authData = auth.device_login().get('AuthenticationResult') # assumes all was OK

accessToken = authData['AccessToken']
refreshToken = authData['RefreshToken']
idToken = authData['IdToken']

But I don't see how I'm supposed to get the data out? The API example suggests I can just use my idToken:

api = Hive.HiveApi(token=idToken)
data = api.getAll()

However, I get a not authorised resposne:

{
  "original": 401,
  "parsed": {
    "error": "NOT_AUTHORIZED"
  }
}

Alternatively, the Session example suggests I can authenticate a different way with my device credentials:

from pyhiveapi import Hive, SMS_REQUIRED

session = Hive(
    username="<Hive Username>",
    password="<Hive Password>",
    deviceGroupKey="<Hive Device Group Key>",
    deviceKey="<Hive Device Key>",
    devicePassword="<Hive Device Password>",
)
session.deviceLogin()
session.startSession()

This raises an exception:

Original exception was:
Traceback (most recent call last):
  File "/Users/jon/Repo/hivereader/./another.py", line 12, in <module>
    session = Hive(
TypeError: __init__() got an unexpected keyword argument 'deviceGroupKey'

I don't see what I can pass the device info to that will then be able to use my token?

Again, sorry if this is just user error!

JonEllis avatar Dec 18 '22 21:12 JonEllis

Hi, I'm also new to this and uncovered the same issue yesterday. Disclaimer: I looked at the code only a few minutes. I may be giving wrong advice!

I expect this is due to the library primarily being focused on home assistant. A few bugs now exist in the session object - looks like a partial refactor of coding conventions moving from CamelCase to '_' Looking at the code there are a couple of things that need to be done for the session object to handle device registrations

First register your device:

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)

session.auth.device_registration('<WhatYouLike>')
deviceData = session.auth.get_device_data()
print(deviceData) # save this info for next time you login

to login using your device details, the code looks for the device key to be set... There are probably better ways to do this...

next time:

from pyhiveapi import Hive, SMS_REQUIRED
DEVICE_REQUIRED = "DEVICE_SRP_AUTH" # not exposed as a seperate const as yet
# create session as normal
session = Hive(
    username="<username>",
    password="<password>",
)
# This time tell the auth object about the device:
session.auth.device_group_key = '<First_Part>'
session.auth.device_key = '<Second_Part>'
session.auth.device_password = '<Third_Part>'
# Now login - should be challenged with device login
login = session.login()
if login.get("ChallengeName") == DEVICE_REQUIRED:
    session.deviceLogin() #Note - this will fail if you do not adapt session.py first - see below!
else:
    print("Are you sure device is registered?")
session.startSession()

There are two bugs I encountered in session.py I think a refactor sometime ago missed a fix to session.py line 282: result = self.auth.deviceLogin() must be updated to result = self.auth.device_login()

To refresh the data for a given device,the method on line 315 (updateData) needs to be adaptedto call async

   async def updateData(self, device: dict):
        """Get latest data for Hive nodes - rate limiting.

        Args:
            device (dict): Device requesting the update.

        Returns:
            boolean: True/False if update was successful
        """
        updated = False
        ep = self.config.lastUpdate + self.config.scanInterval
        if datetime.now() >= ep and not self.updateLock.locked():
            try:
                await self.updateLock.acquire()
                self.getDevices(device["hiveID"])
                if len(self.deviceList["camera"]) > 0:
                    for camera in self.data.camera:
                        self.getCamera(self.devices[camera])
                updated = True
            finally:
                self.updateLock.release()

        return updated

Please note that a number of other functions have changed their signature from the published docs - instead take a look at the code (or rely on pylint)

dantchapman avatar Dec 20 '22 11:12 dantchapman

Thank you dantchapman! I can confirm the problem, and I can confirm your workaround solves the issue. Version 0.5.15 has already resolved replaced .deviceLogin() with device_login() in line 282 of session.py, but the introduction of async and await was still necessary in updateData().

As decribed, the method of registering the device then login, start session and read data works wonders with the MFA. Thanks again.

GandalfTheWhite2 avatar Jun 05 '23 10:06 GandalfTheWhite2

This is excellent; thanks, @dantchapman and @GandalfTheWhite2; I was scratching my head for hours trying to find out how to get deviceLogin() to work after initially logging in under 2FA**.

My application is a Pi with a 433MHz receiver hat so that I can pick up button presses from a cheap remote and then switch Hive Smart Plugs.

Previously after some arbitrary timeout, I would call the login routine again as I didn't have 2FA enabled.

** Now that 2FA is mandatory, I wanted a way of not having to tell the Pi what the 2FA code was from my mobile

This is very much appreciated.

andriodious avatar Jun 06 '23 11:06 andriodious