From 1bb9a5fffbd55e5e4677f09f2eed66db422c802e Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Fri, 25 Sep 2026 11:40:46 -0400 Subject: [PATCH] fix(3cx): omit the bearer token on client-credentials login A refresh was posting the expired access token to /connect/token, and 3CX answered 400. The login request now drops that header. --- src/shared/shared/three_cx_client.py | 7 ++++++- tests/shared/test_three_cx_client.py | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shared/shared/three_cx_client.py b/src/shared/shared/three_cx_client.py index 87a1855..0143a0d 100644 --- a/src/shared/shared/three_cx_client.py +++ b/src/shared/shared/three_cx_client.py @@ -137,6 +137,8 @@ class ThreeCXClient: def _authenticate_oauth(self, client_id: str, client_secret: str): """Authenticate via OAuth2 client credentials (Enterprise license required). API client must be created in 3CX Admin > Integrations > API.""" + # Drop the session bearer. A refresh otherwise sends the expired + # access token to /connect/token, and 3CX answers 400. resp = self._raw_request( "POST", f"{self.base_url}/connect/token", @@ -145,7 +147,10 @@ class ThreeCXClient: "client_secret": client_secret, "grant_type": "client_credentials", }, - headers={"Content-Type": "application/x-www-form-urlencoded"}, + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": None, + }, ) resp.raise_for_status() body = resp.json() diff --git a/tests/shared/test_three_cx_client.py b/tests/shared/test_three_cx_client.py index 75fcf23..457065c 100644 --- a/tests/shared/test_three_cx_client.py +++ b/tests/shared/test_three_cx_client.py @@ -263,7 +263,9 @@ def test_oauth_client_refreshes_expired_token(): queue = client.get_queue("801") assert queue["Id"] == 83 assert client.session.headers["Authorization"] == "Bearer tok-2" - assert len(_token_posts()) == 2 + posts = _token_posts() + assert len(posts) == 2 + assert "Authorization" not in posts[1].request.headers tcx._oauth_clients.clear()