Skip to content

Commit 901c70a

Browse files
committed
Regenerate client credential handling
1 parent f62010c commit 901c70a

File tree

3 files changed

+57
-20
lines changed

3 files changed

+57
-20
lines changed

src/workos/_client.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from ._errors import (
1919
BaseRequestException,
20+
ConfigurationException,
2021
RateLimitExceededException,
2122
ServerException,
2223
WorkOSConnectionException,
@@ -401,16 +402,12 @@ def __init__(
401402
max_retries: int = MAX_RETRIES,
402403
) -> None:
403404
self._api_key = api_key or os.environ.get("WORKOS_API_KEY")
404-
if not self._api_key:
405-
raise ValueError(
406-
"WorkOS API key must be provided when instantiating the client "
407-
"or via the WORKOS_API_KEY environment variable."
408-
)
409405
self.client_id = client_id or os.environ.get("WORKOS_CLIENT_ID")
410-
if not self.client_id:
406+
if not self._api_key and not self.client_id:
411407
raise ValueError(
412-
"WorkOS client ID must be provided when instantiating the client "
413-
"or via the WORKOS_CLIENT_ID environment variable."
408+
"WorkOS requires either an API key or a client ID. "
409+
"Provide api_key / WORKOS_API_KEY for authenticated server-side usage, "
410+
"or client_id / WORKOS_CLIENT_ID for flows that require a client ID."
414411
)
415412
resolved_base_url = base_url or os.environ.get(
416413
"WORKOS_BASE_URL", "https://api.workos.com"
@@ -493,17 +490,34 @@ def _resolve_max_retries(self, request_options: Optional[RequestOptions]) -> int
493490
return retries
494491
return self._max_retries
495492

493+
def _require_api_key(self) -> str:
494+
if not self._api_key:
495+
raise ConfigurationException(
496+
"This operation requires a WorkOS API key. Provide api_key when instantiating the client "
497+
"or via the WORKOS_API_KEY environment variable."
498+
)
499+
return self._api_key
500+
501+
def _require_client_id(self) -> str:
502+
if not self.client_id:
503+
raise ConfigurationException(
504+
"This operation requires a WorkOS client ID. Provide client_id when instantiating the client "
505+
"or via the WORKOS_CLIENT_ID environment variable."
506+
)
507+
return self.client_id
508+
496509
def _build_headers(
497510
self,
498511
method: str,
499512
idempotency_key: Optional[str],
500513
request_options: Optional[RequestOptions],
501514
) -> Dict[str, str]:
502515
headers: Dict[str, str] = {
503-
"Authorization": f"Bearer {self._api_key}",
504516
"Content-Type": "application/json",
505517
"User-Agent": f"workos-python/{VERSION} python/{platform.python_version()}",
506518
}
519+
if self._api_key:
520+
headers["Authorization"] = f"Bearer {self._api_key}"
507521
effective_idempotency_key = idempotency_key
508522
if effective_idempotency_key is None and request_options:
509523
request_option_idempotency_key = request_options.get("idempotency_key")
@@ -652,7 +666,7 @@ def __init__(
652666
max_retries: Maximum number of retries for failed requests. Defaults to 3.
653667
654668
Raises:
655-
ValueError: If api_key is not provided and WORKOS_API_KEY is not set.
669+
ValueError: If neither api_key nor client_id is provided, directly or via environment variables.
656670
"""
657671
super().__init__(
658672
api_key=api_key,
@@ -931,7 +945,7 @@ def __init__(
931945
max_retries: Maximum number of retries for failed requests. Defaults to 3.
932946
933947
Raises:
934-
ValueError: If api_key is not provided and WORKOS_API_KEY is not set.
948+
ValueError: If neither api_key nor client_id is provided, directly or via environment variables.
935949
"""
936950
super().__init__(
937951
api_key=api_key,

src/workos/sso/_resource.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ def authorize(
9191
}.items()
9292
if v is not None
9393
}
94-
params["client_id"] = params.get("client_id") or self._client.client_id
94+
params["client_id"] = (
95+
params.get("client_id") or self._client._require_client_id()
96+
)
9597
return self._client.build_url("sso/authorize", params)
9698

9799
def logout(
@@ -238,8 +240,10 @@ def token(
238240
"code": code,
239241
"grant_type": grant_type,
240242
}
241-
body["client_id"] = body.get("client_id") or self._client.client_id
242-
body["client_secret"] = body.get("client_secret") or self._client._api_key
243+
body["client_id"] = body.get("client_id") or self._client._require_client_id()
244+
body["client_secret"] = (
245+
body.get("client_secret") or self._client._require_api_key()
246+
)
243247
return self._client.request(
244248
method="post",
245249
path="sso/token",
@@ -407,7 +411,9 @@ async def authorize(
407411
}.items()
408412
if v is not None
409413
}
410-
params["client_id"] = params.get("client_id") or self._client.client_id
414+
params["client_id"] = (
415+
params.get("client_id") or self._client._require_client_id()
416+
)
411417
return self._client.build_url("sso/authorize", params)
412418

413419
async def logout(
@@ -554,8 +560,10 @@ async def token(
554560
"code": code,
555561
"grant_type": grant_type,
556562
}
557-
body["client_id"] = body.get("client_id") or self._client.client_id
558-
body["client_secret"] = body.get("client_secret") or self._client._api_key
563+
body["client_id"] = body.get("client_id") or self._client._require_client_id()
564+
body["client_secret"] = (
565+
body.get("client_secret") or self._client._require_api_key()
566+
)
559567
return await self._client.request(
560568
method="post",
561569
path="sso/token",

tests/test_generated_client.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,24 @@
2020

2121

2222
class TestWorkOSClient:
23-
def test_missing_api_key_raises(self):
23+
def test_missing_credentials_raise(self):
2424
with pytest.raises(ValueError):
25-
WorkOS(client_id="client_test")
25+
WorkOS()
2626

2727
def test_context_manager(self):
2828
with WorkOS(api_key="sk_test_123", client_id="client_test") as client:
2929
assert client._api_key == "sk_test_123"
3030

31+
def test_api_key_only_initializes(self):
32+
client = WorkOS(api_key="sk_test_123")
33+
assert client._api_key == "sk_test_123"
34+
assert client.client_id is None
35+
client.close()
36+
3137
def test_client_id_from_constructor(self):
32-
client = WorkOS(api_key="sk_test_123", client_id="client_test_456")
38+
client = WorkOS(client_id="client_test_456")
3339
assert client.client_id == "client_test_456"
40+
assert client._api_key is None
3441
client.close()
3542

3643
def test_raises_400(self, httpx_mock):
@@ -129,6 +136,14 @@ def test_no_idempotency_key_on_get(self, httpx_mock):
129136
assert "Idempotency-Key" not in request.headers
130137
client.close()
131138

139+
def test_no_authorization_header_without_api_key(self, httpx_mock):
140+
httpx_mock.add_response(json={})
141+
client = WorkOS(client_id="client_test")
142+
client.request("GET", "test")
143+
request = httpx_mock.get_request()
144+
assert "Authorization" not in request.headers
145+
client.close()
146+
132147
def test_empty_body_sends_json(self, httpx_mock):
133148
httpx_mock.add_response(json={})
134149
client = WorkOS(api_key="sk_test_123", client_id="client_test")

0 commit comments

Comments
 (0)