Skip to content

Commit 49f6012

Browse files
Merge pull request #282 from vincentwolsink/entrez_token
Use entrez token endpoint for login
2 parents 1a1d7f4 + 78eedf2 commit 49f6012

6 files changed

Lines changed: 28 additions & 101 deletions

File tree

custom_components/enphase_envoy/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
NAME,
4242
PLATFORMS,
4343
CONF_SERIAL,
44-
CONF_TOKEN_ENVOY,
4544
READER,
4645
DEFAULT_SCAN_INTERVAL,
4746
DEFAULT_REALTIME_UPDATE_THROTTLE,
@@ -80,7 +79,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
8079
enlighten_pass=config[CONF_PASSWORD],
8180
inverters=True,
8281
enlighten_serial_num=config[CONF_SERIAL],
83-
token_method_envoy=config.get(CONF_TOKEN_ENVOY, False),
8482
store=store,
8583
disable_negative_production=options.get("disable_negative_production", False),
8684
disabled_endpoints=disabled_endpoints,

custom_components/enphase_envoy/config_flow.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from .const import (
2828
DOMAIN,
2929
CONF_SERIAL,
30-
CONF_TOKEN_ENVOY,
3130
DEFAULT_SCAN_INTERVAL,
3231
DEFAULT_REALTIME_UPDATE_THROTTLE,
3332
ENABLE_ADDITIONAL_METRICS,
@@ -49,7 +48,6 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> EnvoyRead
4948
enlighten_pass=data[CONF_PASSWORD],
5049
inverters=False,
5150
enlighten_serial_num=data[CONF_SERIAL],
52-
token_method_envoy=data.get(CONF_TOKEN_ENVOY, False),
5351
)
5452

5553
try:
@@ -88,7 +86,6 @@ def _async_generate_schema(self):
8886
schema[vol.Required(CONF_SERIAL, default=self.unique_id)] = str
8987
schema[vol.Required(CONF_USERNAME, default=self.username)] = str
9088
schema[vol.Required(CONF_PASSWORD, default="")] = str
91-
schema[vol.Optional(CONF_TOKEN_ENVOY, default=False)] = bool
9289

9390
return vol.Schema(schema)
9491

custom_components/enphase_envoy/const.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
DEFAULT_GETDATA_TIMEOUT = 60
4848

4949
CONF_SERIAL = "serial"
50-
CONF_TOKEN_ENVOY = "token_method_envoy"
5150

5251
LIVE_UPDATEABLE_ENTITIES = "live-update-entities"
5352
ENABLE_ADDITIONAL_METRICS = "enable_additional_metrics"

custom_components/enphase_envoy/envoy_reader.py

Lines changed: 26 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
import base64
1414
import secrets
1515
import string
16+
import re
1617

1718
from jsonpath import jsonpath
18-
from urllib import parse
1919
from json.decoder import JSONDecodeError
2020

2121
from .envoy_endpoints import (
@@ -28,12 +28,9 @@
2828
ENVOY_MODEL_M = "Metered"
2929
ENVOY_MODEL_S = "Standard"
3030

31-
ENLIGHTEN_AUTH_URL = "https://enlighten.enphaseenergy.com/login/login.json?"
32-
ENLIGHTEN_TOKEN_URL = "https://entrez.enphaseenergy.com/tokens"
33-
3431
# paths used for fetching enlighten token through envoy
35-
ENLIGHTEN_LOGIN_URL = "https://entrez.enphaseenergy.com/login"
36-
ENDPOINT_URL_GET_JWT = "https://{}/auth/get_jwt"
32+
ENTREZ_LOGIN_URL = "https://entrez.enphaseenergy.com/login"
33+
ENTREZ_TOKEN_URL = "https://entrez.enphaseenergy.com/entrez_tokens"
3734
ENDPOINT_URL_CHECK_JWT = "https://{}/auth/check_jwt"
3835

3936
_LOGGER = logging.getLogger(__name__)
@@ -787,7 +784,6 @@ def __init__(
787784
disabled_endpoints=[],
788785
lifetime_production_correction=0,
789786
device_data_endpoint="endpoint_device_data",
790-
token_method_envoy=False,
791787
):
792788
"""Init the EnvoyReader."""
793789
self.host = host.lower()
@@ -813,7 +809,6 @@ def __init__(
813809
self.disabled_endpoints = disabled_endpoints
814810
self.lifetime_production_correction = lifetime_production_correction
815811
self.device_data_endpoint = device_data_endpoint
816-
self.token_method_envoy = token_method_envoy
817812

818813
self.uri_registry = {}
819814
for key, endpoint in ENVOY_ENDPOINTS.items():
@@ -975,109 +970,49 @@ async def _async_put(self, url, data, **kwargs):
975970
_LOGGER.debug("TransportError: %s", e)
976971
raise e
977972

978-
async def _fetch_enlighten_token_json(self):
973+
async def _fetch_entrez_token(self):
979974
"""
980-
Try to fetch the token json from Enlighten API
975+
Try to fetch the token json from Entrez
981976
:return:
982977
"""
983978
_LOGGER.debug("Fetching enlighten token")
984979
async with self.async_client as client:
985980
# login to Enlighten
986981
payload_login = {
987-
"user[email]": self.enlighten_user,
988-
"user[password]": self.enlighten_pass,
982+
"username": self.enlighten_user,
983+
"password": self.enlighten_pass,
989984
}
990-
resp = await client.post(ENLIGHTEN_AUTH_URL, data=payload_login, timeout=30)
991-
if resp.status_code >= 400:
985+
login_resp = await client.post(
986+
ENTREZ_LOGIN_URL, data=payload_login, timeout=30
987+
)
988+
if login_resp.status_code >= 400:
992989
raise EnlightenError("Could not Authenticate via Enlighten")
993990

994991
# now that we're in a logged in session, we can request the installer token
995-
login_data = resp.json()
996992
payload_token = {
997-
"session_id": login_data["session_id"],
998-
"serial_num": self.enlighten_serial_num,
999-
"username": self.enlighten_user,
993+
"serialNum": self.enlighten_serial_num,
1000994
}
1001-
resp = await client.post(
1002-
ENLIGHTEN_TOKEN_URL, json=payload_token, timeout=30
995+
token_resp = await client.post(
996+
ENTREZ_TOKEN_URL,
997+
data=payload_token,
998+
timeout=30,
999+
cookies=login_resp.cookies,
10031000
)
1004-
if resp.status_code != 200:
1001+
if token_resp.status_code != 200:
10051002
raise EnlightenError("Could not get enlighten token")
1006-
return resp.text
10071003

1008-
async def _fetch_envoy_token_json(self):
1009-
"""
1010-
Fetch a token, using the same procedure envoy uses in the webUI
1011-
1012-
:returns received access_token
1013-
"""
1014-
_LOGGER.debug("Fetching envoy token")
1015-
async with self.async_client as client:
1016-
# Step 1, generate local secret
1017-
code_verifier = random_content(40)
1018-
1019-
_LOGGER.debug("Local auth secret: %s", code_verifier)
1020-
1021-
# Step 2, call the entrez login with form fields
1022-
# all params are reverse engineered, so prone to changes
1023-
login_data = dict(
1024-
username=self.enlighten_user,
1025-
password=self.enlighten_pass,
1026-
codeChallenge=generate_challenge(code_verifier),
1027-
redirectUri=f"https://{self.host}/auth/callback",
1028-
client="envoy-ui",
1029-
clientId="envoy-ui-client",
1030-
authFlow="oauth",
1031-
serialNum=self.enlighten_serial_num,
1032-
granttype="authorize",
1033-
state="",
1034-
invalidSerialNum="",
1004+
match = re.search(
1005+
r"<textarea[^>]*>(.*?)</textarea>", token_resp.text, re.DOTALL
10351006
)
1036-
_LOGGER.debug(
1037-
"Doing authorize at entrez, with codeChallenge: %s",
1038-
login_data["codeChallenge"],
1039-
)
1040-
resp = await client.post(ENLIGHTEN_LOGIN_URL, data=login_data)
1041-
1042-
if resp.status_code >= 400:
1043-
raise EnlightenError("Could not Login via Enlighten")
1044-
1045-
# we should expect a 302 redirect
1046-
if resp.status_code != 302:
1047-
raise EnlightenError("Login did not succeed")
1048-
1049-
# Step 3: Fetch the code from the query params.
1050-
redirect_location = resp.headers.get("location")
1051-
url_parts = parse.urlparse(redirect_location)
1052-
query_parts = parse.parse_qs(url_parts.query)
1053-
1054-
# Step 4: Fetch the JWT token through envoy
1055-
json_data = {
1056-
"client_id": "envoy-ui-1",
1057-
"code": query_parts["code"][0],
1058-
"code_verifier": code_verifier,
1059-
"grant_type": "authorization_code",
1060-
"redirect_uri": login_data["redirectUri"],
1061-
}
1062-
_LOGGER.debug("Checking JWT on envoy with params %s", json_data)
1063-
resp = await client.post(
1064-
ENDPOINT_URL_GET_JWT.format(self.host),
1065-
json=json_data,
1066-
timeout=30,
1067-
)
1068-
1069-
if resp.status_code != 200:
1070-
raise EnvoyError(
1071-
f"Could not fetch access token from envoy; HTTP {resp.status_code}: {resp.text}"
1072-
)
1007+
if match:
1008+
token = match.group(1).strip()
1009+
else:
1010+
raise EnlightenError("Could not get enlighten token")
10731011

1074-
return resp.json()["access_token"]
1012+
return token
10751013

10761014
async def _get_enphase_token(self):
1077-
if self.token_method_envoy:
1078-
self._token = await self._fetch_envoy_token_json()
1079-
else:
1080-
self._token = await self._fetch_enlighten_token_json()
1015+
self._token = await self._fetch_entrez_token()
10811016

10821017
_LOGGER.debug("Envoy Token")
10831018
if self._is_enphase_token_expired(self._token):

custom_components/enphase_envoy/translations/en.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
"host": "Host",
1717
"password": "Enlighten Password",
1818
"username": "Enlighten Username",
19-
"serial": "Envoy Serial Number",
20-
"token_method_envoy": "Simulate local Web UI login (required for DIY account)"
19+
"serial": "Envoy Serial Number"
2120
},
2221
"description": "Enter the hostname/ip and serial of your Envoy. Use your Enlighten (installer) account credentials."
2322
}

custom_components/enphase_envoy/translations/nl.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
"host": "Host",
1717
"password": "Enlighten Wachtwoord",
1818
"username": "Enlighten Gebruikersnaam",
19-
"serial": "Envoy Serienummer",
20-
"token_method_envoy": "Simuleer lokale Web UI login (vereist voor DHZ account)"
19+
"serial": "Envoy Serienummer"
2120
},
2221
"description": "Voer de hostname/ip en serienummer van je Envoy in. Gebruik je Enlighten (installer) account gegevens."
2322
}

0 commit comments

Comments
 (0)