66import logging
77from typing import Any
88
9- from blinkpy .auth import Auth , LoginError , TokenRefreshFailed
9+ from blinkpy .auth import Auth , BlinkTwoFARequiredError , LoginError , TokenRefreshFailed
1010from blinkpy .blinkpy import Blink , BlinkSetupError
1111import voluptuous as vol
1212
13- from homeassistant .config_entries import SOURCE_REAUTH , ConfigFlow , ConfigFlowResult
13+ from homeassistant .config_entries import (
14+ SOURCE_REAUTH ,
15+ SOURCE_RECONFIGURE ,
16+ ConfigFlow ,
17+ ConfigFlowResult ,
18+ )
1419from homeassistant .const import CONF_PASSWORD , CONF_PIN , CONF_USERNAME
15- from homeassistant .core import HomeAssistant , callback
20+ from homeassistant .core import callback
1621from homeassistant .exceptions import HomeAssistantError
1722from homeassistant .helpers .aiohttp_client import async_get_clientsession
1823
2126_LOGGER = logging .getLogger (__name__ )
2227
2328
24- async def validate_input (auth : Auth ) -> None :
29+ async def validate_input (blink : Blink ) -> None :
2530 """Validate the user input allows us to connect."""
2631 try :
27- await auth . startup ()
32+ await blink . start ()
2833 except (LoginError , TokenRefreshFailed ) as err :
2934 raise InvalidAuth from err
30- if auth .check_key_required ():
31- raise Require2FA
3235
3336
34- async def _send_blink_2fa_pin (hass : HomeAssistant , auth : Auth , pin : str | None ) -> bool :
37+ async def _send_blink_2fa_pin (blink : Blink , pin : str | None ) -> bool :
3538 """Send 2FA pin to blink servers."""
36- blink = Blink (session = async_get_clientsession (hass ))
37- blink .auth = auth
38- blink .setup_login_ids ()
39- blink .setup_urls ()
40- return await auth .send_auth_key (blink , pin )
39+ await blink .send_2fa_code (pin )
40+ return True
4141
4242
4343class BlinkConfigFlow (ConfigFlow , domain = DOMAIN ):
@@ -48,26 +48,33 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
4848 def __init__ (self ) -> None :
4949 """Initialize the blink flow."""
5050 self .auth : Auth | None = None
51+ self .blink : Blink | None = None
52+
53+ async def _handle_user_input (self , user_input : dict [str , Any ]):
54+ """Handle user input."""
55+ self .auth = Auth (
56+ {** user_input , "device_id" : DEVICE_ID },
57+ no_prompt = True ,
58+ session = async_get_clientsession (self .hass ),
59+ )
60+ self .blink = Blink (session = async_get_clientsession (self .hass ))
61+ self .blink .auth = self .auth
62+ await self .async_set_unique_id (user_input [CONF_USERNAME ])
63+ if self .source not in (SOURCE_REAUTH , SOURCE_RECONFIGURE ):
64+ self ._abort_if_unique_id_configured ()
65+
66+ await validate_input (self .blink )
67+ return self ._async_finish_flow ()
5168
5269 async def async_step_user (
5370 self , user_input : dict [str , Any ] | None = None
5471 ) -> ConfigFlowResult :
5572 """Handle a flow initiated by the user."""
5673 errors = {}
5774 if user_input is not None :
58- self .auth = Auth (
59- {** user_input , "device_id" : DEVICE_ID },
60- no_prompt = True ,
61- session = async_get_clientsession (self .hass ),
62- )
63- await self .async_set_unique_id (user_input [CONF_USERNAME ])
64- if self .source != SOURCE_REAUTH :
65- self ._abort_if_unique_id_configured ()
66-
6775 try :
68- await validate_input (self .auth )
69- return self ._async_finish_flow ()
70- except Require2FA :
76+ return await self ._handle_user_input (user_input )
77+ except BlinkTwoFARequiredError :
7178 return await self .async_step_2fa ()
7279 except InvalidAuth :
7380 errors ["base" ] = "invalid_auth"
@@ -93,19 +100,16 @@ async def async_step_2fa(
93100 errors = {}
94101 if user_input is not None :
95102 try :
96- valid_token = await _send_blink_2fa_pin (
97- self .hass , self .auth , user_input .get (CONF_PIN )
98- )
103+ await _send_blink_2fa_pin (self .blink , user_input .get (CONF_PIN ))
99104 except BlinkSetupError :
100105 errors ["base" ] = "cannot_connect"
106+ except TokenRefreshFailed :
107+ errors ["base" ] = "invalid_access_token"
101108 except Exception :
102109 _LOGGER .exception ("Unexpected exception" )
103110 errors ["base" ] = "unknown"
104-
105111 else :
106- if valid_token :
107- return self ._async_finish_flow ()
108- errors ["base" ] = "invalid_access_token"
112+ return self ._async_finish_flow ()
109113
110114 return self .async_show_form (
111115 step_id = "2fa" ,
@@ -118,18 +122,88 @@ async def async_step_2fa(
118122 async def async_step_reauth (
119123 self , entry_data : Mapping [str , Any ]
120124 ) -> ConfigFlowResult :
121- """Perform reauth upon migration of old entries."""
122- return await self .async_step_user (dict (entry_data ))
125+ """Perform reauth after an authentication error."""
126+ return await self .async_step_reauth_confirm (None )
127+
128+ async def async_step_reauth_confirm (
129+ self , user_input : dict [str , Any ] | None = None
130+ ) -> ConfigFlowResult :
131+ """Handle reauth confirmation."""
132+ errors = {}
133+ if user_input is not None :
134+ try :
135+ return await self ._handle_user_input (user_input )
136+ except BlinkTwoFARequiredError :
137+ return await self .async_step_2fa ()
138+ except InvalidAuth :
139+ errors ["base" ] = "invalid_auth"
140+ except Exception :
141+ _LOGGER .exception ("Unexpected exception" )
142+ errors ["base" ] = "unknown"
143+
144+ config_entry = self ._get_reauth_entry ()
145+ return self .async_show_form (
146+ step_id = "reauth_confirm" ,
147+ data_schema = vol .Schema (
148+ {
149+ vol .Required (
150+ CONF_USERNAME , default = config_entry .data [CONF_USERNAME ]
151+ ): str ,
152+ vol .Required (
153+ CONF_PASSWORD , default = config_entry .data [CONF_PASSWORD ]
154+ ): str ,
155+ }
156+ ),
157+ errors = errors ,
158+ description_placeholders = {"username" : config_entry .data [CONF_USERNAME ]},
159+ )
160+
161+ async def async_step_reconfigure (
162+ self , user_input : dict [str , Any ] | None = None
163+ ) -> ConfigFlowResult :
164+ """Handle reconfiguration initiated by the user."""
165+ errors = {}
166+ if user_input is not None :
167+ try :
168+ return await self ._handle_user_input (user_input )
169+ except BlinkTwoFARequiredError :
170+ return await self .async_step_2fa ()
171+ except InvalidAuth :
172+ errors ["base" ] = "invalid_auth"
173+ except Exception :
174+ _LOGGER .exception ("Unexpected exception" )
175+ errors ["base" ] = "unknown"
176+
177+ config_entry = self ._get_reconfigure_entry ()
178+ return self .async_show_form (
179+ step_id = "reconfigure" ,
180+ data_schema = vol .Schema (
181+ {
182+ vol .Required (
183+ CONF_USERNAME , default = config_entry .data [CONF_USERNAME ]
184+ ): str ,
185+ vol .Required (
186+ CONF_PASSWORD , default = config_entry .data [CONF_PASSWORD ]
187+ ): str ,
188+ }
189+ ),
190+ errors = errors ,
191+ )
123192
124193 @callback
125194 def _async_finish_flow (self ) -> ConfigFlowResult :
126195 """Finish with setup."""
127196 assert self .auth
128- return self .async_create_entry (title = DOMAIN , data = self .auth .login_attributes )
129197
198+ if self .source in (SOURCE_REAUTH , SOURCE_RECONFIGURE ):
199+ return self .async_update_reload_and_abort (
200+ self ._get_reauth_entry ()
201+ if self .source == SOURCE_REAUTH
202+ else self ._get_reconfigure_entry (),
203+ data_updates = self .auth .login_attributes ,
204+ )
130205
131- class Require2FA (HomeAssistantError ):
132- """Error to indicate we require 2FA."""
206+ return self .async_create_entry (title = DOMAIN , data = self .auth .login_attributes )
133207
134208
135209class InvalidAuth (HomeAssistantError ):
0 commit comments