22
33from __future__ import annotations
44
5+ from collections .abc import Mapping
56import logging
67from typing import Any
78
89from pycync import Auth
910from pycync .exceptions import AuthFailedError , CyncError , TwoFactorRequiredError
1011import voluptuous as vol
1112
12- from homeassistant .config_entries import ConfigFlow , ConfigFlowResult
13+ from homeassistant .config_entries import SOURCE_REAUTH , ConfigFlow , ConfigFlowResult
1314from homeassistant .const import CONF_ACCESS_TOKEN , CONF_EMAIL , CONF_PASSWORD
1415from homeassistant .helpers .aiohttp_client import async_get_clientsession
1516
@@ -39,37 +40,22 @@ class CyncConfigFlow(ConfigFlow, domain=DOMAIN):
3940
4041 VERSION = 1
4142
42- cync_auth : Auth
43+ cync_auth : Auth = None
4344
4445 async def async_step_user (
4546 self , user_input : dict [str , Any ] | None = None
4647 ) -> ConfigFlowResult :
4748 """Attempt login with user credentials."""
4849 errors : dict [str , str ] = {}
4950
50- if user_input is None :
51- return self .async_show_form (
52- step_id = "user" , data_schema = STEP_USER_DATA_SCHEMA , errors = errors
53- )
51+ if user_input :
52+ try :
53+ errors = await self ._validate_credentials (user_input )
54+ except TwoFactorRequiredError :
55+ return await self .async_step_two_factor ()
5456
55- self .cync_auth = Auth (
56- async_get_clientsession (self .hass ),
57- username = user_input [CONF_EMAIL ],
58- password = user_input [CONF_PASSWORD ],
59- )
60- try :
61- await self .cync_auth .login ()
62- except AuthFailedError :
63- errors ["base" ] = "invalid_auth"
64- except TwoFactorRequiredError :
65- return await self .async_step_two_factor ()
66- except CyncError :
67- errors ["base" ] = "cannot_connect"
68- except Exception :
69- _LOGGER .exception ("Unexpected exception" )
70- errors ["base" ] = "unknown"
71- else :
72- return await self ._create_config_entry (self .cync_auth .username )
57+ if not errors :
58+ return await self ._create_config_entry (self .cync_auth .username )
7359
7460 return self .async_show_form (
7561 step_id = "user" , data_schema = STEP_USER_DATA_SCHEMA , errors = errors
@@ -81,38 +67,95 @@ async def async_step_two_factor(
8167 """Attempt login with the two factor auth code sent to the user."""
8268 errors : dict [str , str ] = {}
8369
84- if user_input is None :
70+ if user_input :
71+ errors = await self ._validate_credentials (user_input )
72+
73+ if not errors :
74+ return await self ._create_config_entry (self .cync_auth .username )
75+
8576 return self .async_show_form (
86- step_id = "two_factor" , data_schema = STEP_TWO_FACTOR_SCHEMA , errors = errors
77+ step_id = "user" , data_schema = STEP_USER_DATA_SCHEMA , errors = errors
78+ )
79+
80+ return self .async_show_form (
81+ step_id = "two_factor" , data_schema = STEP_TWO_FACTOR_SCHEMA , errors = errors
82+ )
83+
84+ async def async_step_reauth (
85+ self , entry_data : Mapping [str , Any ]
86+ ) -> ConfigFlowResult :
87+ """Perform reauth upon an API authentication error."""
88+ return await self .async_step_reauth_confirm ()
89+
90+ async def async_step_reauth_confirm (
91+ self , user_input : dict [str , Any ] | None = None
92+ ) -> ConfigFlowResult :
93+ """Dialog that informs the user that reauth is required and prompts for their Cync credentials."""
94+ errors : dict [str , str ] = {}
95+
96+ reauth_entry = self ._get_reauth_entry ()
97+
98+ if user_input :
99+ try :
100+ errors = await self ._validate_credentials (user_input )
101+ except TwoFactorRequiredError :
102+ return await self .async_step_two_factor ()
103+
104+ if not errors :
105+ return await self ._create_config_entry (self .cync_auth .username )
106+
107+ return self .async_show_form (
108+ step_id = "reauth_confirm" ,
109+ data_schema = STEP_USER_DATA_SCHEMA ,
110+ errors = errors ,
111+ description_placeholders = {CONF_EMAIL : reauth_entry .title },
112+ )
113+
114+ async def _validate_credentials (self , user_input : dict [str , Any ]) -> dict [str , str ]:
115+ """Attempt to log in with user email and password, and return the error dict."""
116+ errors : dict [str , str ] = {}
117+
118+ if not self .cync_auth :
119+ self .cync_auth = Auth (
120+ async_get_clientsession (self .hass ),
121+ username = user_input [CONF_EMAIL ],
122+ password = user_input [CONF_PASSWORD ],
87123 )
124+
88125 try :
89- await self .cync_auth .login (user_input [CONF_TWO_FACTOR_CODE ])
126+ await self .cync_auth .login (user_input .get (CONF_TWO_FACTOR_CODE ))
127+ except TwoFactorRequiredError :
128+ raise
90129 except AuthFailedError :
91130 errors ["base" ] = "invalid_auth"
92131 except CyncError :
93132 errors ["base" ] = "cannot_connect"
94133 except Exception :
95134 _LOGGER .exception ("Unexpected exception" )
96135 errors ["base" ] = "unknown"
97- else :
98- return await self ._create_config_entry (self .cync_auth .username )
99136
100- return self .async_show_form (
101- step_id = "user" , data_schema = STEP_USER_DATA_SCHEMA , errors = errors
102- )
137+ return errors
103138
104139 async def _create_config_entry (self , user_email : str ) -> ConfigFlowResult :
105140 """Create the Cync config entry using input user data."""
106141
107142 cync_user = self .cync_auth .user
108143 await self .async_set_unique_id (str (cync_user .user_id ))
109- self ._abort_if_unique_id_configured ()
110144
111- config = {
145+ config_data = {
112146 CONF_USER_ID : cync_user .user_id ,
113147 CONF_AUTHORIZE_STRING : cync_user .authorize ,
114148 CONF_EXPIRES_AT : cync_user .expires_at ,
115149 CONF_ACCESS_TOKEN : cync_user .access_token ,
116150 CONF_REFRESH_TOKEN : cync_user .refresh_token ,
117151 }
118- return self .async_create_entry (title = user_email , data = config )
152+
153+ if self .source == SOURCE_REAUTH :
154+ self ._abort_if_unique_id_mismatch ()
155+ return self .async_update_reload_and_abort (
156+ entry = self ._get_reauth_entry (), title = user_email , data = config_data
157+ )
158+
159+ self ._abort_if_unique_id_configured ()
160+
161+ return self .async_create_entry (title = user_email , data = config_data )
0 commit comments