From 5db820cf16877b38f7a005e3e53e23387c39e4ae Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 8 Jul 2026 14:57:12 +0000 Subject: [PATCH] feat: raise NoOverkizUserError for RESOURCE_ACCESS_DENIED "No such user account" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A valid Somfy account can authenticate via OAuth yet be rejected on the first authenticated Overkiz call (e.g. POST events/register during login) with a 403 RESOURCE_ACCESS_DENIED "No such user account" — because the plain SSO token is not bound to an Overkiz user on the classic endpoint. These accounts need the site-scoped multi-account flow. Previously this fell through to a bare ResourceAccessDeniedError. Add a dedicated NoOverkizUserError subclass, mapped only under RESOURCE_ACCESS_DENIED so the existing AUTHENTICATION_ERROR "No such user account" -> UnknownUserError login path (unsupported-hardware / Somfy Protect) is unchanged. --- pyoverkiz/exceptions.py | 9 +++++++++ pyoverkiz/response_handler.py | 2 ++ tests/fixtures/exceptions/cloud/no-overkiz-user.json | 4 ++++ tests/test_client.py | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 tests/fixtures/exceptions/cloud/no-overkiz-user.json diff --git a/pyoverkiz/exceptions.py b/pyoverkiz/exceptions.py index a416f007..c260bdcf 100644 --- a/pyoverkiz/exceptions.py +++ b/pyoverkiz/exceptions.py @@ -128,6 +128,15 @@ class ApplicationNotAllowedError(ResourceAccessDeniedError): """Raised when the setup cannot be accessed through the application.""" +class NoOverkizUserError(ResourceAccessDeniedError): + """Raised when the account authenticated but has no Overkiz user on this server. + + Login succeeds, but the token is not bound to an Overkiz user on this + endpoint. Seen for Somfy accounts that need the site-scoped multi-account + flow rather than the classic single-endpoint login. + """ + + # Nexity class NexityBadCredentialsError(BadCredentialsError): """Raised when invalid credentials are provided to Nexity authentication API.""" diff --git a/pyoverkiz/response_handler.py b/pyoverkiz/response_handler.py index ed2ded42..95b84ba9 100644 --- a/pyoverkiz/response_handler.py +++ b/pyoverkiz/response_handler.py @@ -21,6 +21,7 @@ MaintenanceError, MissingAPIKeyError, MissingAuthorizationTokenError, + NoOverkizUserError, NoRegisteredEventListenerError, NoSuchActionGroupError, NoSuchDeviceError, @@ -61,6 +62,7 @@ ("AUTHENTICATION_ERROR", "API key is required", MissingAPIKeyError), ("AUTHENTICATION_ERROR", "No such user account", UnknownUserError), ("RESOURCE_ACCESS_DENIED", "Not authenticated", NotAuthenticatedError), + ("RESOURCE_ACCESS_DENIED", "No such user account", NoOverkizUserError), ( "RESOURCE_ACCESS_DENIED", "Missing authorization token", diff --git a/tests/fixtures/exceptions/cloud/no-overkiz-user.json b/tests/fixtures/exceptions/cloud/no-overkiz-user.json new file mode 100644 index 00000000..9303d1e4 --- /dev/null +++ b/tests/fixtures/exceptions/cloud/no-overkiz-user.json @@ -0,0 +1,4 @@ +{ + "errorCode": "RESOURCE_ACCESS_DENIED", + "error": "No such user account : user@example.tld" +} diff --git a/tests/test_client.py b/tests/test_client.py index 0a818346..c556c0b2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -630,6 +630,11 @@ async def test_get_diagnostic_data_returns_structured_dict( exceptions.UnknownUserError, 400, ), + ( + "cloud/no-overkiz-user.json", + exceptions.NoOverkizUserError, + 403, + ), ( "cloud/no-such-command.json", exceptions.InvalidCommandError,