1212 API_AUTH_REQUEST_PASSWORD_JSON_KEY ,
1313 API_AUTH_REQUEST_SUBSCRIPTION_KEY_HEADER ,
1414 API_AUTH_REQUEST_SUBSCRIPTION_KEY_HEADER_VALUE ,
15- API_AUTH_STATUS_JSON_KEY ,
16- API_AUTH_STATUS_OK ,
1715 API_AUTH_TOKEN_JSON_KEY ,
16+ API_AUTH_MESSAGE_JSON_KEY ,
17+ API_AUTH_STATUS_JSON_KEY ,
1818 API_CLUB_VISITS_ENDPOINT_FORMATSTRING ,
1919 API_CLUB_VISITS_ENDPOINT_DATE_FORMAT ,
2020 API_CLUB_VISITS_AUTH_HEADER ,
21+ AuthenticationResults ,
22+ AUTHENTICATION_RESPONSE_MESSAGES ,
23+ AUTHENTICATION_RESPONSE_STATUSES
2124)
2225
2326_LOGGER = logging .getLogger (__name__ )
2427
2528
29+ def handle_authentication_response_json (response_json : dict ):
30+ # Based on https://my.lifetime.life/components/login/index.js
31+ message = response_json .get (API_AUTH_MESSAGE_JSON_KEY )
32+ status = response_json .get (API_AUTH_STATUS_JSON_KEY )
33+ if message == AUTHENTICATION_RESPONSE_MESSAGES [AuthenticationResults .SUCCESS ]:
34+ return response_json [API_AUTH_TOKEN_JSON_KEY ]
35+ elif message == AUTHENTICATION_RESPONSE_MESSAGES [AuthenticationResults .PASSWORD_NEEDS_TO_BE_CHANGED ]:
36+ if API_AUTH_TOKEN_JSON_KEY in response_json :
37+ _LOGGER .warning ("Life Time password needs to be changed, but API can still be used" )
38+ return response_json [API_AUTH_TOKEN_JSON_KEY ]
39+ else :
40+ raise ApiPasswordNeedsToBeChanged
41+ elif (
42+ status == AUTHENTICATION_RESPONSE_STATUSES [AuthenticationResults .INVALID ] or
43+ message == AUTHENTICATION_RESPONSE_MESSAGES [AuthenticationResults .INVALID ]
44+ ):
45+ raise ApiInvalidAuth
46+ elif status == AUTHENTICATION_RESPONSE_STATUSES [AuthenticationResults .TOO_MANY_ATTEMPTS ]:
47+ raise ApiTooManyAuthenticationAttempts
48+ elif status == AUTHENTICATION_RESPONSE_STATUSES [AuthenticationResults .ACTIVATION_REQUIRED ]:
49+ raise ApiActivationRequired
50+ elif status == AUTHENTICATION_RESPONSE_STATUSES [AuthenticationResults .DUPLICATE_EMAIL ]:
51+ raise ApiDuplicateEmail
52+ _LOGGER .error ("Received unknown authentication error in response: %s" , response_json )
53+ raise ApiUnknownAuthError
54+
55+
2656class Api :
2757 def __init__ (self , hass , username : str , password : str ) -> None :
2858 self ._username = username
@@ -49,18 +79,12 @@ async def authenticate(self):
4979 },
5080 ) as response :
5181 response_json = await response .json ()
52- if (
53- API_AUTH_STATUS_JSON_KEY not in response_json
54- or response_json [API_AUTH_STATUS_JSON_KEY ] != API_AUTH_STATUS_OK
55- ):
56- _LOGGER .error ("Received invalid authentication response: %s" , response_json )
57- raise ApiInvalidAuth
58- self ._sso_token = response_json [API_AUTH_TOKEN_JSON_KEY ]
82+ self ._sso_token = handle_authentication_response_json (response_json )
5983 except ClientResponseError as err :
6084 if err .status == HTTPStatus .UNAUTHORIZED :
61- _LOGGER .exception ("Received invalid authentication status: %d" , err .status )
6285 raise ApiInvalidAuth
63- raise err
86+ _LOGGER .error ("Received unknown status code in authentication response: %d" , err .status )
87+ raise ApiUnknownAuthError
6488 except ClientConnectionError :
6589 _LOGGER .exception ("Connection error while authenticating to Life Time API" )
6690 raise ApiCannotConnect
@@ -106,10 +130,30 @@ class ApiCannotConnect(Exception):
106130 """Client can't connect to API server"""
107131
108132
133+ class ApiPasswordNeedsToBeChanged (Exception ):
134+ """Password needs to be changed"""
135+
136+
137+ class ApiTooManyAuthenticationAttempts (Exception ):
138+ """There were too many authentication attempts"""
139+
140+
141+ class ApiActivationRequired (Exception ):
142+ """Account activation required"""
143+
144+
145+ class ApiDuplicateEmail (Exception ):
146+ """There are multiple accounts associated with this email"""
147+
148+
109149class ApiInvalidAuth (Exception ):
110150 """API server returned invalid auth"""
111151
112152
153+ class ApiUnknownAuthError (Exception ):
154+ """API server returned unknown error"""
155+
156+
113157class ApiAuthRequired (Exception ):
114158 """This API call requires authenticating beforehand"""
115159
0 commit comments