Skip to content

Add Somfy multi-account (multi-site) authentication#2168

Open
iMicknl wants to merge 7 commits into
mainfrom
feat/somfy-multi-account
Open

Add Somfy multi-account (multi-site) authentication#2168
iMicknl wants to merge 7 commits into
mainfrom
feat/somfy-multi-account

Conversation

@iMicknl

@iMicknl iMicknl commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Adds Somfy multi-account (multi-site) support — the "multi account sign-in" feature in the current TaHoma app — letting a single Somfy account authenticate and control each of its sites (homes) through pyoverkiz, reusing every existing Overkiz endpoint call unchanged.

Introduces a new region-agnostic Server.SOMFY backed by a SomfyAccountAuthStrategy. Legacy per-region servers (SOMFY_EUROPE/AMERICA/OCEANIA) remain for classic single-site password login and the local API.

How it works

  • Password grant + Keycloak (Ginaite) token exchange — no browser, no PKCE, no redirect URI; fits the existing username/password config flow. Reuses the SOMFY_CLIENT_ID pyoverkiz already stores.
  • Site discovery via the BOB site directory; sites/sub-sites/gateways are flattened into gateway candidates.
  • Region resolved client-side from a static country → region map mirroring the TaHoma app's BusinessArea.fromCountry (EMEA/APAC/SNABA). No API field carries the region. Any unresolvable country falls back to EMEA (identical to the app) and logs a warning so the map can be updated.
  • Per-site token minting — a site-scoped Ginaite token is a plain Bearer the classic Overkiz enduser API accepts, so all existing endpoint calls work unchanged. Tokens expire in 900s; re-scoping happens on relogin.
  • Warm-start credentials to skip rediscovery when the target site is already known.

Invitations are intentionally deferred (YAGNI — no HA consumer yet).

Testing

  • uv run pytest — 579 passed (99 in tests/test_auth.py, extensively covering the new strategy).
  • Pre-commit hooks (ruff, mypy, ty) pass.

Add Server.SOMFY with a region-agnostic multi-site auth strategy that lets
a single Somfy account authenticate and control each of its sites through
pyoverkiz, reusing every existing Overkiz endpoint call.

- Password grant + Keycloak (Ginaite) token exchange, no browser/PKCE
- Site discovery via the BOB directory; region resolved from a static
  country->region map mirroring the TaHoma app, with EMEA fallback
- Per-site token minting and re-scoping on relogin
- Warm-start credentials to skip rediscovery when the site is known
@iMicknl iMicknl requested a review from tetienne as a code owner July 5, 2026 22:12
Copilot AI review requested due to automatic review settings July 5, 2026 22:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the feature New feature or capability label Jul 5, 2026
@iMicknl iMicknl linked an issue Jul 5, 2026 that may be closed by this pull request
…tart to resume/fresh

Replace the hand-walked triple-nested dict traversal in discover_gateways
with typed BobSite/BobSubSite/BobGateway models parsed by a dedicated BOB
cattrs converter, flattened via BobSitesResponse.gateway_candidates().
Carry each site's country on GatewayCandidate, dropping the parallel
_site_country side-channel that discover_gateways and select_gateway shared.

Also rename the warm/cold-start terminology to the industry-standard
resume/fresh-login: SomfyTokenCredentials now yields a resumed session,
_warm_start -> _resume_session, warm_start_credentials() -> to_credentials().
@iMicknl iMicknl changed the title feat: add Somfy multi-account (multi-site) authentication Add Somfy multi-account (multi-site) authentication Jul 5, 2026
iMicknl added 2 commits July 5, 2026 22:36
…rim docstrings

Add SupportsSessionResume + OverkizClient.to_credentials() so the Somfy
resume flow works through the public API instead of reaching into the
private auth strategy. Document multi-account login and session resume in
the getting-started guide. Condense the verbose docstrings/comments added
earlier in this branch to one-liners.
@iMicknl

iMicknl commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

Integration feedback — wiring this branch into Home Assistant

I built the Home Assistant Server.SOMFY config flow + runtime setup against this branch (token-only persistence, discover_gateways()select_gateway()to_credentials(), on_token_refresh write-back). Overall the design maps cleanly onto HA and I'd like to finalize it. A few things are worth resolving on the library side first, ordered by impact.

1. _resume_session region lookup can raise an uncaught KeyError 🐛

self._endpoint = SOMFY_REGION_ENDPOINT[credentials.region]  # strategies.py

On resume, region comes straight from persisted config-entry storage. A stale/corrupt/renamed region string makes this a bare KeyError raised inside client.login(). On the HA side that call sits in async_setup_entry's try block, which only catches typed exceptions (BadCredentialsError, TooManyRequestsError, …) — so a KeyError escapes as an ungraceful setup crash instead of a clean retry/reauth.

Note the selection path (_region_for_country) is already defensive — .get() + warning + EMEA fallback. The resume path should match it. Suggest:

  • SOMFY_REGION_ENDPOINT.get(region) and raise a typed SomfyServiceError on miss, and/or
  • make region a StrEnum so it's validated on the way back in.

This is the highest-value change: it converts a latent crash into a typed, catchable error.

2. Expose selected_gateway on the client + document post-login() state

The property exists on the strategy but isn't surfaced on OverkizClient. Because of that the integration re-derives the "how many gateways came back / did login() already auto-select one" logic the library already does internally. Surfacing client.selected_gateway — plus a short note on exactly what state login() leaves behind for single vs. multi-site — lets integrators call to_credentials() straight after login() in the sole-site case without re-running discovery/select.

3. Make the rotation footgun louder than a docstring

Omitting on_token_refresh works silently until the first refresh-token rotation, then a later resume fails. It's only documented on SomfyTokenCredentials. A one-time warning on the first rotation when no callback is wired would save integrators a confusing debugging session.

4. Clarify the legacy SOMFY_EUROPE vs. new SOMFY end state (the main open question)

SomfyAuthStrategy (legacy per-region, single-site, password-on-disk) and SomfyAccountAuthStrategy (new, multi-site, token-only) now coexist, and both SOMFY_EUROPE and SOMFY appear in HA's server picker — so a user sees "Somfy Europe" and "Somfy" side by side with no guidance on which to pick. The library can't fix the picker, but the intended trajectory should be documented: is SOMFY_EUROPE (and AMERICA/OCEANIA) deprecated in favor of SOMFY? That decision drives whether HA needs a migration/repair flow, so pinning it down here unblocks the integration side.


The new-strategy test coverage is genuinely thorough (resume roundtrip, rotation-notify, region fallback, relogin-rescope) — no gaps there. #1 is the only functional concern; the rest are ergonomics/docs.

iMicknl added 2 commits July 6, 2026 13:07
A revoked refresh token (e.g. after a password change) returns a 400
invalid_grant on the site-scoped refresh grant. Classify it as bad
credentials, mirroring the password grant and CozyTouch strategy, so
callers trigger reauth instead of surfacing an unexpected error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Somfy multi-account (Ginaite/BOB) authentication flow

2 participants