feat(auth): replace cookie scraping with Auth0 OAuth + DPoP-bound refresh#267
feat(auth): replace cookie scraping with Auth0 OAuth + DPoP-bound refresh#267sslivins wants to merge 3 commits intobinarydev:mainfrom
Conversation
…resh Replaces the manual cookie-extraction auth flow with the same Auth0 Universal Login + DPoP-bound refresh token flow the MyGenerac mobile app uses. Adds an email/password config flow, an in-place reauth step, a configurable scan_interval option, and bumps the API surface to v5 to match the app. - New auth.py: Auth0 Universal Login + DPoP token client - API endpoints bumped to v5 - Email/password config flow with reauth step - Options flow for scan_interval (default 900s) - Coordinator passes config_entry to silence HA 2025+ warning - entity: device_state_attributes -> extra_state_attributes - sensor: _safe_float helper for malformed numeric props - image: guard against missing content-type header - Tests updated end-to-end for new auth and api surfaces Existing entries hit reauth on next poll and prompt for MyGenerac email/password once. Tokens are then persisted in the config entry and refreshed automatically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tity, sensors - auth.py: refresh-token success/forbidden/invalid_grant/invalid_request paths plus 7 form-parser cases for the legacy CSRF login flow. - coordinator.py: default + override scan_interval; InvalidCredentialsException and InvalidGrantError both surface as ConfigEntryAuthFailed. - __init__.py: persist callback wired into auth on setup; first-refresh InvalidCredentialsException / InvalidGrantError raise ConfigEntryAuthFailed. - config_flow.py: reauth flow keeps original email (not user-supplied) when calling login(); reconfigure persists CONF_SCAN_INTERVAL into entry.options. - entity.py: missing device id falls back to _EMPTY_ITEM (available=False). - sensor.py: _safe_float treats None / 'N/A' / non-numeric as None instead of crashing native_value (RunTime, ProtectionTime, BatteryVoltage). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use hass.config_entries.async_setup() so entry transitions through SETUP_IN_PROGRESS, matching the pattern of existing tests in this file. Calling async_setup_entry directly when entry is NOT_LOADED triggers OperationNotAllowed in async_forward_entry_setups. - Remove blank line after module docstring per reorder-python-imports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Quick follow-up - pushed two more commits adding test coverage for the auth changes (refresh-token flow, hidden-input form parser, error paths) plus some defensive bits in the coordinator and sensor code ( |
|
Hit a snag using this PR with my MyGenerac account: after Account had a one-time consent form pending after the Generac→Auth0 migration. Cleared it interactively in the MobileLink mobile app first (form completion is account-bound; once done, Auth0 skips the prompt for subsequent OAuth flows from any client). Then patched The custom-prompt page is React-rendered (no static If the prompt requires actual interactive action (file upload, MFA setup, profile completion), POST returns 200 with the page again — the handler raises a clear error pointing the user to the MobileLink app. Patch (against 8c550ca): auth.py diff (272 lines, mostly the new _handle_custom_prompt + a few extra step= log lines for diagnostics)$(cat /tmp/generac-prompt-patch.diff)64/64 tests still pass. Happy to open this as a PR against your |
Hey! First off, thanks for starting and maintaining this integration.
I've been running it for a while and it's been genuinely useful. Really
appreciate the work you've put in. 🙏
That said, the cookie thing has been a recurring headache. Every few
weeks something on Generac's portal shifts (or the cookie just ages
out) and I'm back in DevTools copying values out of a browser, and I
noticed a lot of the recent issues here are folks hitting the same
wall. So I took a stab at fixing it.
My first attempt was actually a script that drove the web login and
got around the bot challenge in front of it. That worked surprisingly
well, but it was still a manual thing I had to run whenever the cookie
died, which kind of defeated the point. Since I'm reasonably familiar
with OAuth, I figured I'd peek at how the mobile app authenticates and
see if it was simple enough to port over. Turned out to be very doable,
and fortunately once you're past the login the API surface the app
talks to is identical to what this integration already uses. So I
pointed the integration at the same Auth0 Universal Login the app
uses, with the same DPoP-bound refresh token flow. End result: real
OAuth, no cookie extraction, automatic token refresh, and a proper
reauth prompt if a refresh ever fails (e.g. password change) instead
of a silently-broken entry.
One thing worth flagging: the app talks to the v5 API where the
integration was on an older version, so I switched everything over to
v5 while I was in there. Nothing I use seems broken by it, but you'll
probably want to take a closer look since you know the surface better
than I do.
What's in here
auth.py(new): Auth0 Universal Login + DPoP token client.(in-place, no remove-and-re-add).
scan_interval(default 900s / 15min,matching the previous behavior).
config_entrytoDataUpdateCoordinatorsuper, which silences the HA 2025+ deprecation warning.
device_state_attributestoextra_state_attributes,drop a redundant listener wire-up that
CoordinatorEntityalreadyhandles.
_safe_floathelper so a malformed numeric propreports
unknowninstead of throwing innative_value.content-typeheader.including Auth0 error parsing.
scan interval option.
Migration
No silent fallback. After upgrading, existing entries land in reauth
on the next poll and prompt for MyGenerac email + password once. From
then on tokens are persisted in the config entry and refreshed
automatically.
Testing
pytest -p no:timeout: full suite green locally (Windows).polls; saw the token refresh at the expected
expires_inboundary;sensors update normally; reauth re-authenticates without losing the
entry.
Follow-ups
I've got a few other small PRs coming to clean up some nits I noticed
along the way. Feel free to take or leave whatever you don't want.
Thanks again for the project, and no rush on review. Happy to iterate
on whatever you want changed.