11"""Login handler for blink."""
22
3+ import time
34import logging
45from aiohttp import (
56 ClientSession ,
1415 APP_BUILD ,
1516 DEFAULT_USER_AGENT ,
1617 LOGIN_ENDPOINT ,
18+ TIER_ENDPOINT ,
1719 TIMEOUT ,
1820)
1921
@@ -45,12 +47,16 @@ def __init__(
4547 login_data = {}
4648 self .data = login_data
4749 self .token = login_data .get ("token" , None )
50+ self .expires_in = login_data .get ("expires_in" , None )
51+ self .expiration_date = None
52+ self ._refresh_token = login_data .get ("refresh_token" , None )
4853 self .host = login_data .get ("host" , None )
4954 self .region_id = login_data .get ("region_id" , None )
5055 self .client_id = login_data .get ("client_id" , None )
5156 self .account_id = login_data .get ("account_id" , None )
5257 self .user_id = login_data .get ("user_id" , None )
5358 self .login_response = None
59+ self .tier_info = None
5460 self .is_errored = False
5561 self .no_prompt = no_prompt
5662 self ._agent = agent
@@ -61,6 +67,8 @@ def __init__(
6167 def login_attributes (self ):
6268 """Return a dictionary of login attributes."""
6369 self .data ["token" ] = self .token
70+ self .data ["expires_in" ] = self .expires_in
71+ self .data ["refresh_token" ] = self ._refresh_token
6472 self .data ["host" ] = self .host
6573 self .data ["region_id" ] = self .region_id
6674 self .data ["client_id" ] = self .client_id
@@ -74,9 +82,9 @@ def header(self):
7482 if self .token is None :
7583 return None
7684 return {
77- "APP-BUILD" : self ._app_build ,
78- "TOKEN_AUTH " : self .token ,
79- "User-Agent" : self ._agent ,
85+ # "APP-BUILD": self._app_build,
86+ "Authorization " : f"Bearer { self .token } " ,
87+ # "User-Agent": self._agent,
8088 "Content-Type" : "application/json" ,
8189 }
8290
@@ -88,23 +96,36 @@ def validate_login(self):
8896 self .data = util .prompt_login_data (self .data )
8997 self .data = util .validate_login_data (self .data )
9098
91- async def login (self , login_url = LOGIN_ENDPOINT ):
92- """Attempt login to blink servers."""
99+ def validate_2fa (self ):
100+ """Check 2FA information and prompt if not available."""
101+ self .data ["2fa_code" ] = self .data .get ("2fa_code" , None )
102+ if not self .no_prompt :
103+ self .data = util .prompt_2fa_data (self .data )
104+
105+ async def login (self , login_url = LOGIN_ENDPOINT , refresh = False ):
106+ """Attempt OAuth login to blink servers."""
93107 self .validate_login ()
94- _LOGGER .info ("Attempting login with %s" , login_url )
95108 response = await api .request_login (
96109 self ,
97110 login_url ,
98111 self .data ,
112+ is_refresh = refresh ,
99113 is_retry = False ,
100114 )
101115 try :
102116 if response .status == 200 :
103117 return await response .json ()
118+ if response .status == 412 :
119+ self .validate_2fa ()
120+ return await self .login ()
104121 raise LoginError
105122 except AttributeError as error :
106123 raise LoginError from error
107124
125+ async def get_tier_info (self , tier_url = TIER_ENDPOINT ):
126+ """Get tier information."""
127+ return await api .request_tier (self , tier_url )
128+
108129 def logout (self , blink ):
109130 """Log out."""
110131 return api .request_logout (blink )
@@ -116,6 +137,8 @@ async def refresh_token(self):
116137 _LOGGER .info ("Token expired, attempting automatic refresh." )
117138 self .login_response = await self .login ()
118139 self .extract_login_info ()
140+ self .tier_info = await self .get_tier_info ()
141+ self .extract_tier_info ()
119142 self .is_errored = False
120143 except LoginError as error :
121144 _LOGGER .error ("Login endpoint failed. Try again later." )
@@ -127,12 +150,16 @@ async def refresh_token(self):
127150
128151 def extract_login_info (self ):
129152 """Extract login info from login response."""
130- self .region_id = self .login_response ["account" ]["tier" ]
153+ self .token = self .login_response ["access_token" ]
154+ self .expires_in = self .login_response ["expires_in" ]
155+ self .expiration_date = time .time () + self .expires_in
156+ self ._refresh_token = self .login_response ["refresh_token" ]
157+
158+ def extract_tier_info (self ):
159+ """Extract tier info from tier info response."""
160+ self .region_id = self .tier_info ["tier" ]
131161 self .host = f"{ self .region_id } .{ BLINK_URL } "
132- self .token = self .login_response ["auth" ]["token" ]
133- self .client_id = self .login_response ["account" ]["client_id" ]
134- self .account_id = self .login_response ["account" ]["account_id" ]
135- self .user_id = self .login_response ["account" ].get ("user_id" , None )
162+ self .account_id = self .tier_info ["account_id" ]
136163
137164 async def startup (self ):
138165 """Initialize tokens for communication."""
@@ -161,6 +188,13 @@ async def validate_response(self, response: ClientResponse, json_resp):
161188 self .is_errored = False
162189 return json_data
163190
191+ def need_refresh (self ):
192+ """Check if token needs refresh."""
193+ if self .expiration_date is None :
194+ return self ._refresh_token is not None
195+
196+ return self .expiration_date - time .time () < 60
197+
164198 async def query (
165199 self ,
166200 url = None ,
@@ -171,6 +205,7 @@ async def query(
171205 json_resp = True ,
172206 is_retry = False ,
173207 timeout = TIMEOUT ,
208+ skip_refresh_check = False ,
174209 ):
175210 """Perform server requests.
176211
@@ -183,6 +218,9 @@ async def query(
183218 :param is_retry: Is this part of a re-auth attempt? True/FALSE
184219 """
185220 try :
221+ if not skip_refresh_check and self .need_refresh ():
222+ await self .login (refresh = True )
223+
186224 if reqtype == "get" :
187225 response = await self .session .get (
188226 url = url , data = data , headers = headers , timeout = timeout
@@ -231,33 +269,6 @@ async def query(
231269 _LOGGER .error ("Unable to refresh token." )
232270 return None
233271
234- async def send_auth_key (self , blink , key ):
235- """Send 2FA key to blink servers."""
236- if key is not None :
237- response = await api .request_verify (self , blink , key )
238- try :
239- json_resp = await response .json ()
240- blink .available = json_resp ["valid" ]
241- if not blink .available :
242- _LOGGER .error ("%s" , json_resp ["message" ])
243- return False
244- except (KeyError , TypeError , ContentTypeError ) as er :
245- _LOGGER .error (
246- "Did not receive valid response from server. Error: %s" ,
247- er ,
248- )
249- return False
250- return True
251-
252- def check_key_required (self ):
253- """Check if 2FA key is required."""
254- try :
255- if self .login_response ["account" ]["client_verification_required" ]:
256- return True
257- except (KeyError , TypeError ):
258- pass
259- return False
260-
261272
262273class TokenRefreshFailed (Exception ):
263274 """Class to throw failed refresh exception."""
0 commit comments