diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 6dc3b99f..5232cb40 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -1,7 +1,7 @@ { "version": 2, "language": "python", - "generatedAt": "2026-05-06T23:30:58.090Z", + "generatedAt": "2026-05-11T15:56:51.952Z", "files": [ "src/workos/_client.py", "src/workos/admin_portal/__init__.py", diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index 83c18d39..198c29d6 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -9,7 +9,8 @@ import random from datetime import datetime, timezone from email.utils import parsedate_to_datetime -from typing import Any, Dict, Optional, Type, cast, overload +from typing import Any, Dict, Optional, Sequence, Type, cast, overload +from urllib.parse import quote import httpx @@ -53,8 +54,15 @@ def __init__( request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, + is_public: bool = False, ) -> None: - self._api_key = api_key or os.environ.get("WORKOS_API_KEY") + self._is_public = is_public + # Public clients (PKCE / browser / mobile / CLI) must never attach + # an API key, even if WORKOS_API_KEY is present in the environment. + if is_public: + self._api_key: Optional[str] = None + else: + self._api_key = api_key or os.environ.get("WORKOS_API_KEY") self.client_id = client_id or os.environ.get("WORKOS_CLIENT_ID") if not self._api_key and not self.client_id: raise ValueError( @@ -80,12 +88,14 @@ def base_url(self) -> str: """The base URL for API requests.""" return self._base_url - def build_url(self, path: str, params: Optional[Dict[str, Any]] = None) -> str: + def build_url( + self, path: Sequence[str], params: Optional[Dict[str, Any]] = None + ) -> str: """Build a full URL with query parameters for redirect/authorization endpoints.""" from urllib.parse import urlencode base = self._base_url.rstrip("/") - url = f"{base}/{path}" + url = f"{base}/{self._encode_path(path)}" if params: url = f"{url}?{urlencode(params)}" return url @@ -128,6 +138,27 @@ def _resolve_base_url(self, request_options: Optional[RequestOptions]) -> str: return str(base_url).rstrip("/") return self._base_url.rstrip("/") + @staticmethod + def _encode_path(path: Sequence[str]) -> str: + """Percent-encode each path segment and join with ``/``. + + Callers pass each path component as a separate element (e.g. + ``("organizations", organization_id)``). Each element is URL-encoded + with ``safe=""`` so a caller-supplied id containing ``/``, ``?``, + ``#``, ``%``, or ``..`` cannot escape its intended segment — this is + the structural protection against forged cross-resource API requests + under the application's API key. + + A bare string would be silently iterable as a sequence of single + characters; we reject it explicitly so a forgotten tuple wrapper at a + call site fails loudly instead of producing a per-character URL. + """ + if isinstance(path, str): + raise TypeError( + "path must be a sequence of segments (e.g. a tuple), not a str" + ) + return "/".join(quote(str(seg), safe="") for seg in path) + def _resolve_timeout(self, request_options: Optional[RequestOptions]) -> float: timeout = self._request_timeout if request_options: @@ -332,6 +363,7 @@ def __init__( request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, + is_public: bool = False, ) -> None: """Initialize the WorkOS client. @@ -342,6 +374,10 @@ def __init__( request_timeout: HTTP request timeout in seconds. Falls back to WORKOS_REQUEST_TIMEOUT or 60. jwt_leeway: JWT clock skew leeway in seconds. max_retries: Maximum number of retries for failed requests. Defaults to 3. + is_public: When True, mark this client as public (PKCE / browser + / mobile / CLI). The API key is forced to None and the + ``WORKOS_API_KEY`` environment variable is ignored. Use + ``create_public_client`` instead of setting this directly. Raises: ValueError: If neither api_key nor client_id is provided, directly or via environment variables. @@ -353,6 +389,7 @@ def __init__( request_timeout=request_timeout, jwt_leeway=jwt_leeway, max_retries=max_retries, + is_public=is_public, ) self._client = httpx.Client( timeout=self._request_timeout, follow_redirects=True @@ -372,7 +409,7 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: def request( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = ..., @@ -385,7 +422,7 @@ def request( def request( self, method: str, - path: str, + path: Sequence[str], *, model: None = ..., params: Optional[Dict[str, Any]] = ..., @@ -397,7 +434,7 @@ def request( def request( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -406,7 +443,7 @@ def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{path}" + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" headers = self._build_headers(method, idempotency_key, request_options) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) @@ -453,7 +490,7 @@ def request( def request_raw( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -478,7 +515,7 @@ def request_raw( def request_list( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -500,14 +537,14 @@ def request_list( ) if not isinstance(result, list): raise WorkOSError( - f"Expected array response from {method.upper()} /{path}, got {type(result).__name__}" + f"Expected array response from {method.upper()} /{'/'.join(path)}, got {type(result).__name__}" ) return result def request_page( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = None, @@ -557,6 +594,7 @@ def __init__( request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, + is_public: bool = False, ) -> None: """Initialize the async WorkOS client. @@ -578,6 +616,7 @@ def __init__( request_timeout=request_timeout, jwt_leeway=jwt_leeway, max_retries=max_retries, + is_public=is_public, ) self._client = httpx.AsyncClient( timeout=self._request_timeout, follow_redirects=True @@ -597,7 +636,7 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: async def request( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = ..., @@ -610,7 +649,7 @@ async def request( async def request( self, method: str, - path: str, + path: Sequence[str], *, model: None = ..., params: Optional[Dict[str, Any]] = ..., @@ -622,7 +661,7 @@ async def request( async def request( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -631,7 +670,7 @@ async def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an async HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{path}" + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" headers = self._build_headers(method, idempotency_key, request_options) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) @@ -678,7 +717,7 @@ async def request( async def request_raw( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -703,7 +742,7 @@ async def request_raw( async def request_list( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -725,14 +764,14 @@ async def request_list( ) if not isinstance(result, list): raise WorkOSError( - f"Expected array response from {method.upper()} /{path}, got {type(result).__name__}" + f"Expected array response from {method.upper()} /{'/'.join(path)}, got {type(result).__name__}" ) return result async def request_page( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = None, diff --git a/src/workos/actions.py b/src/workos/actions.py index cd2b3b7f..258d7986 100644 --- a/src/workos/actions.py +++ b/src/workos/actions.py @@ -44,7 +44,7 @@ def _verify_signature( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > tolerance: + if abs(seconds_since_issued) > tolerance: raise ValueError("Timestamp outside the tolerance zone") body_str = payload.decode("utf-8") if isinstance(payload, bytes) else payload diff --git a/src/workos/admin_portal/_resource.py b/src/workos/admin_portal/_resource.py index 57e4233d..c6bf0ec9 100644 --- a/src/workos/admin_portal/_resource.py +++ b/src/workos/admin_portal/_resource.py @@ -77,7 +77,7 @@ def generate_link( } return self._client.request( method="post", - path="portal/generate_link", + path=("portal", "generate_link"), body=body, model=PortalLinkResponse, request_options=request_options, @@ -149,7 +149,7 @@ async def generate_link( } return await self._client.request( method="post", - path="portal/generate_link", + path=("portal", "generate_link"), body=body, model=PortalLinkResponse, request_options=request_options, diff --git a/src/workos/api_keys/_resource.py b/src/workos/api_keys/_resource.py index e72adfb6..b0598e73 100644 --- a/src/workos/api_keys/_resource.py +++ b/src/workos/api_keys/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -67,7 +66,7 @@ def list_organization_api_keys( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), model=OrganizationApiKey, params=params, request_options=request_options, @@ -111,7 +110,7 @@ def create_organization_api_key( } return self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), body=body, model=OrganizationApiKeyWithValue, request_options=request_options, @@ -145,7 +144,7 @@ def create_validation( } return self._client.request( method="post", - path="api_keys/validations", + path=("api_keys", "validations"), body=body, model=ApiKeyValidationResponse, request_options=request_options, @@ -173,7 +172,7 @@ def delete_api_key( """ self._client.request( method="delete", - path=f"api_keys/{quote(str(id), safe='')}", + path=("api_keys", str(id)), request_options=request_options, ) @@ -227,7 +226,7 @@ async def list_organization_api_keys( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), model=OrganizationApiKey, params=params, request_options=request_options, @@ -271,7 +270,7 @@ async def create_organization_api_key( } return await self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), body=body, model=OrganizationApiKeyWithValue, request_options=request_options, @@ -305,7 +304,7 @@ async def create_validation( } return await self._client.request( method="post", - path="api_keys/validations", + path=("api_keys", "validations"), body=body, model=ApiKeyValidationResponse, request_options=request_options, @@ -333,6 +332,6 @@ async def delete_api_key( """ await self._client.request( method="delete", - path=f"api_keys/{quote(str(id), safe='')}", + path=("api_keys", str(id)), request_options=request_options, ) diff --git a/src/workos/audit_logs/_resource.py b/src/workos/audit_logs/_resource.py index 9c7dc15b..db5352b6 100644 --- a/src/workos/audit_logs/_resource.py +++ b/src/workos/audit_logs/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -54,7 +53,7 @@ def get_organization_audit_logs_retention( """ return self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), model=AuditLogsRetentionJson, request_options=request_options, ) @@ -90,7 +89,7 @@ def update_organization_audit_logs_retention( } return self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), body=body, model=AuditLogsRetentionJson, request_options=request_options, @@ -138,7 +137,7 @@ def list_actions( } return self._client.request_page( method="get", - path="audit_logs/actions", + path=("audit_logs", "actions"), model=AuditLogActionJson, params=params, request_options=request_options, @@ -188,7 +187,7 @@ def list_action_schemas( } return self._client.request_page( method="get", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), model=AuditLogSchemaJson, params=params, request_options=request_options, @@ -234,7 +233,7 @@ def create_schema( } return self._client.request( method="post", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), body=body, model=AuditLogSchemaJson, request_options=request_options, @@ -281,7 +280,7 @@ def create_event( } return self._client.request( method="post", - path="audit_logs/events", + path=("audit_logs", "events"), body=body, model=AuditLogEventCreateResponse, idempotency_key=idempotency_key, @@ -341,7 +340,7 @@ def create_export( } return self._client.request( method="post", - path="audit_logs/exports", + path=("audit_logs", "exports"), body=body, model=AuditLogExportJson, request_options=request_options, @@ -372,7 +371,7 @@ def get_export( """ return self._client.request( method="get", - path=f"audit_logs/exports/{quote(str(audit_log_export_id), safe='')}", + path=("audit_logs", "exports", str(audit_log_export_id)), model=AuditLogExportJson, request_options=request_options, ) @@ -409,7 +408,7 @@ async def get_organization_audit_logs_retention( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), model=AuditLogsRetentionJson, request_options=request_options, ) @@ -445,7 +444,7 @@ async def update_organization_audit_logs_retention( } return await self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), body=body, model=AuditLogsRetentionJson, request_options=request_options, @@ -493,7 +492,7 @@ async def list_actions( } return await self._client.request_page( method="get", - path="audit_logs/actions", + path=("audit_logs", "actions"), model=AuditLogActionJson, params=params, request_options=request_options, @@ -543,7 +542,7 @@ async def list_action_schemas( } return await self._client.request_page( method="get", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), model=AuditLogSchemaJson, params=params, request_options=request_options, @@ -589,7 +588,7 @@ async def create_schema( } return await self._client.request( method="post", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), body=body, model=AuditLogSchemaJson, request_options=request_options, @@ -636,7 +635,7 @@ async def create_event( } return await self._client.request( method="post", - path="audit_logs/events", + path=("audit_logs", "events"), body=body, model=AuditLogEventCreateResponse, idempotency_key=idempotency_key, @@ -696,7 +695,7 @@ async def create_export( } return await self._client.request( method="post", - path="audit_logs/exports", + path=("audit_logs", "exports"), body=body, model=AuditLogExportJson, request_options=request_options, @@ -727,7 +726,7 @@ async def get_export( """ return await self._client.request( method="get", - path=f"audit_logs/exports/{quote(str(audit_log_export_id), safe='')}", + path=("audit_logs", "exports", str(audit_log_export_id)), model=AuditLogExportJson, request_options=request_options, ) diff --git a/src/workos/authorization/_resource.py b/src/workos/authorization/_resource.py index b55c3acc..cd7afcd8 100644 --- a/src/workos/authorization/_resource.py +++ b/src/workos/authorization/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -117,7 +116,12 @@ def check( body["resource_type_slug"] = resource_target.resource_type_slug return self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/check", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "check", + ), body=body, model=AuthorizationCheck, request_options=request_options, @@ -185,7 +189,12 @@ def list_resources_for_membership( ) return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + ), model=AuthorizationResource, params=params, request_options=request_options, @@ -238,7 +247,14 @@ def list_effective_permissions( } return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -293,7 +309,15 @@ def list_effective_permissions_by_external_id( } return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_type_slug), + str(external_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -343,7 +367,12 @@ def list_role_assignments( } return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -388,7 +417,12 @@ def assign_role( body["resource_type_slug"] = resource_target.resource_type_slug return self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, model=UserRoleAssignment, request_options=request_options, @@ -430,7 +464,12 @@ def remove_role( body["resource_type_slug"] = resource_target.resource_type_slug self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, request_options=request_options, ) @@ -460,7 +499,13 @@ def remove_role_assignment( """ self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments/{quote(str(role_assignment_id), safe='')}", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + str(role_assignment_id), + ), request_options=request_options, ) @@ -490,7 +535,7 @@ def list_organization_roles( """ return self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), model=RoleList, request_options=request_options, ) @@ -542,7 +587,7 @@ def create_organization_role( } return self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), body=body, model=Role, request_options=request_options, @@ -576,7 +621,13 @@ def get_organization_role( """ return self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), model=Role, request_options=request_options, ) @@ -623,7 +674,13 @@ def update_organization_role( } return self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), body=body, model=Role, request_options=request_options, @@ -656,7 +713,13 @@ def delete_organization_role( """ self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), request_options=request_options, ) @@ -695,7 +758,14 @@ def add_organization_role_permission( } return self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -735,7 +805,14 @@ def set_organization_role_permissions( } return self._client.request( method="put", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -768,7 +845,15 @@ def remove_organization_role_permission( """ self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions/{quote(str(permission_slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + str(permission_slug), + ), request_options=request_options, ) @@ -802,7 +887,14 @@ def get_resource_by_external_id( """ return self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), model=AuthorizationResource, request_options=request_options, ) @@ -866,7 +958,14 @@ def update_resource_by_external_id( ) return self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), body=body, model=AuthorizationResource, request_options=request_options, @@ -910,7 +1009,14 @@ def delete_resource_by_external_id( } self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), params=params, request_options=request_options, ) @@ -973,7 +1079,15 @@ def list_memberships_for_resource_by_external_id( } return self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/organization_memberships", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -1027,7 +1141,15 @@ def list_role_assignments_for_resource_by_external_id( } return self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/role_assignments", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -1095,7 +1217,7 @@ def list_resources( params["parent_external_id"] = parent.parent_external_id return self._client.request_page( method="get", - path="authorization/resources", + path=("authorization", "resources"), model=AuthorizationResource, params=params, request_options=request_options, @@ -1163,7 +1285,7 @@ def create_resource( ) return self._client.request( method="post", - path="authorization/resources", + path=("authorization", "resources"), body=body, model=AuthorizationResource, request_options=request_options, @@ -1196,7 +1318,7 @@ def get_resource( """ return self._client.request( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), model=AuthorizationResource, request_options=request_options, ) @@ -1256,7 +1378,7 @@ def update_resource( ) return self._client.request( method="patch", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), body=body, model=AuthorizationResource, request_options=request_options, @@ -1296,7 +1418,7 @@ def delete_resource( } self._client.request( method="delete", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), params=params, request_options=request_options, ) @@ -1355,7 +1477,12 @@ def list_memberships_for_resource( } return self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/organization_memberships", + path=( + "authorization", + "resources", + str(resource_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -1405,7 +1532,7 @@ def list_role_assignments_for_resource( } return self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/role_assignments", + path=("authorization", "resources", str(resource_id), "role_assignments"), model=UserRoleAssignment, params=params, request_options=request_options, @@ -1431,7 +1558,7 @@ def list_environment_roles( """ return self._client.request( method="get", - path="authorization/roles", + path=("authorization", "roles"), model=RoleList, request_options=request_options, ) @@ -1481,7 +1608,7 @@ def create_environment_role( } return self._client.request( method="post", - path="authorization/roles", + path=("authorization", "roles"), body=body, model=Role, request_options=request_options, @@ -1513,7 +1640,7 @@ def get_environment_role( """ return self._client.request( method="get", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), model=Role, request_options=request_options, ) @@ -1558,7 +1685,7 @@ def update_environment_role( } return self._client.request( method="patch", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), body=body, model=Role, request_options=request_options, @@ -1597,7 +1724,7 @@ def add_environment_role_permission( } return self._client.request( method="post", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -1636,7 +1763,7 @@ def set_environment_role_permissions( } return self._client.request( method="put", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -1683,7 +1810,7 @@ def list_permissions( } return self._client.request_page( method="get", - path="authorization/permissions", + path=("authorization", "permissions"), model=AuthorizationPermission, params=params, request_options=request_options, @@ -1733,7 +1860,7 @@ def create_permission( } return self._client.request( method="post", - path="authorization/permissions", + path=("authorization", "permissions"), body=body, model=Permission, request_options=request_options, @@ -1764,7 +1891,7 @@ def get_permission( """ return self._client.request( method="get", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), model=AuthorizationPermission, request_options=request_options, ) @@ -1808,7 +1935,7 @@ def update_permission( } return self._client.request( method="patch", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), body=body, model=AuthorizationPermission, request_options=request_options, @@ -1837,7 +1964,7 @@ def delete_permission( """ self._client.request( method="delete", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), request_options=request_options, ) @@ -1887,7 +2014,12 @@ async def check( body["resource_type_slug"] = resource_target.resource_type_slug return await self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/check", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "check", + ), body=body, model=AuthorizationCheck, request_options=request_options, @@ -1955,7 +2087,12 @@ async def list_resources_for_membership( ) return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + ), model=AuthorizationResource, params=params, request_options=request_options, @@ -2008,7 +2145,14 @@ async def list_effective_permissions( } return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -2063,7 +2207,15 @@ async def list_effective_permissions_by_external_id( } return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_type_slug), + str(external_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -2113,7 +2265,12 @@ async def list_role_assignments( } return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -2158,7 +2315,12 @@ async def assign_role( body["resource_type_slug"] = resource_target.resource_type_slug return await self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, model=UserRoleAssignment, request_options=request_options, @@ -2200,7 +2362,12 @@ async def remove_role( body["resource_type_slug"] = resource_target.resource_type_slug await self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, request_options=request_options, ) @@ -2230,7 +2397,13 @@ async def remove_role_assignment( """ await self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments/{quote(str(role_assignment_id), safe='')}", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + str(role_assignment_id), + ), request_options=request_options, ) @@ -2260,7 +2433,7 @@ async def list_organization_roles( """ return await self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), model=RoleList, request_options=request_options, ) @@ -2312,7 +2485,7 @@ async def create_organization_role( } return await self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), body=body, model=Role, request_options=request_options, @@ -2346,7 +2519,13 @@ async def get_organization_role( """ return await self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), model=Role, request_options=request_options, ) @@ -2393,7 +2572,13 @@ async def update_organization_role( } return await self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), body=body, model=Role, request_options=request_options, @@ -2426,7 +2611,13 @@ async def delete_organization_role( """ await self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), request_options=request_options, ) @@ -2465,7 +2656,14 @@ async def add_organization_role_permission( } return await self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -2505,7 +2703,14 @@ async def set_organization_role_permissions( } return await self._client.request( method="put", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -2538,7 +2743,15 @@ async def remove_organization_role_permission( """ await self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions/{quote(str(permission_slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + str(permission_slug), + ), request_options=request_options, ) @@ -2572,7 +2785,14 @@ async def get_resource_by_external_id( """ return await self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), model=AuthorizationResource, request_options=request_options, ) @@ -2636,7 +2856,14 @@ async def update_resource_by_external_id( ) return await self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), body=body, model=AuthorizationResource, request_options=request_options, @@ -2680,7 +2907,14 @@ async def delete_resource_by_external_id( } await self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), params=params, request_options=request_options, ) @@ -2743,7 +2977,15 @@ async def list_memberships_for_resource_by_external_id( } return await self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/organization_memberships", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -2797,7 +3039,15 @@ async def list_role_assignments_for_resource_by_external_id( } return await self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/role_assignments", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -2865,7 +3115,7 @@ async def list_resources( params["parent_external_id"] = parent.parent_external_id return await self._client.request_page( method="get", - path="authorization/resources", + path=("authorization", "resources"), model=AuthorizationResource, params=params, request_options=request_options, @@ -2933,7 +3183,7 @@ async def create_resource( ) return await self._client.request( method="post", - path="authorization/resources", + path=("authorization", "resources"), body=body, model=AuthorizationResource, request_options=request_options, @@ -2966,7 +3216,7 @@ async def get_resource( """ return await self._client.request( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), model=AuthorizationResource, request_options=request_options, ) @@ -3026,7 +3276,7 @@ async def update_resource( ) return await self._client.request( method="patch", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), body=body, model=AuthorizationResource, request_options=request_options, @@ -3066,7 +3316,7 @@ async def delete_resource( } await self._client.request( method="delete", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), params=params, request_options=request_options, ) @@ -3125,7 +3375,12 @@ async def list_memberships_for_resource( } return await self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/organization_memberships", + path=( + "authorization", + "resources", + str(resource_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -3175,7 +3430,7 @@ async def list_role_assignments_for_resource( } return await self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/role_assignments", + path=("authorization", "resources", str(resource_id), "role_assignments"), model=UserRoleAssignment, params=params, request_options=request_options, @@ -3201,7 +3456,7 @@ async def list_environment_roles( """ return await self._client.request( method="get", - path="authorization/roles", + path=("authorization", "roles"), model=RoleList, request_options=request_options, ) @@ -3251,7 +3506,7 @@ async def create_environment_role( } return await self._client.request( method="post", - path="authorization/roles", + path=("authorization", "roles"), body=body, model=Role, request_options=request_options, @@ -3283,7 +3538,7 @@ async def get_environment_role( """ return await self._client.request( method="get", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), model=Role, request_options=request_options, ) @@ -3328,7 +3583,7 @@ async def update_environment_role( } return await self._client.request( method="patch", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), body=body, model=Role, request_options=request_options, @@ -3367,7 +3622,7 @@ async def add_environment_role_permission( } return await self._client.request( method="post", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -3406,7 +3661,7 @@ async def set_environment_role_permissions( } return await self._client.request( method="put", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -3453,7 +3708,7 @@ async def list_permissions( } return await self._client.request_page( method="get", - path="authorization/permissions", + path=("authorization", "permissions"), model=AuthorizationPermission, params=params, request_options=request_options, @@ -3503,7 +3758,7 @@ async def create_permission( } return await self._client.request( method="post", - path="authorization/permissions", + path=("authorization", "permissions"), body=body, model=Permission, request_options=request_options, @@ -3534,7 +3789,7 @@ async def get_permission( """ return await self._client.request( method="get", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), model=AuthorizationPermission, request_options=request_options, ) @@ -3578,7 +3833,7 @@ async def update_permission( } return await self._client.request( method="patch", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), body=body, model=AuthorizationPermission, request_options=request_options, @@ -3607,6 +3862,6 @@ async def delete_permission( """ await self._client.request( method="delete", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), request_options=request_options, ) diff --git a/src/workos/connect/_resource.py b/src/workos/connect/_resource.py index be9036cd..0e41ab7d 100644 --- a/src/workos/connect/_resource.py +++ b/src/workos/connect/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -83,7 +82,7 @@ def complete_oauth2( } return self._client.request( method="post", - path="authkit/oauth2/complete", + path=("authkit", "oauth2", "complete"), body=body, model=ExternalAuthCompleteResponse, request_options=request_options, @@ -133,7 +132,7 @@ def list_applications( } return self._client.request_page( method="get", - path="connect/applications", + path=("connect", "applications"), model=ConnectApplication, params=params, request_options=request_options, @@ -166,7 +165,7 @@ def create_application( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return self._client.request( method="post", - path="connect/applications", + path=("connect", "applications"), body=_body, model=ConnectApplication, request_options=request_options, @@ -203,7 +202,7 @@ def create_oauth_application( return self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -231,7 +230,7 @@ def create_m2m_application( return self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -262,7 +261,7 @@ def get_application( """ return self._client.request( method="get", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), model=ConnectApplication, request_options=request_options, ) @@ -313,7 +312,7 @@ def update_application( } return self._client.request( method="put", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), body=body, model=ConnectApplication, request_options=request_options, @@ -341,7 +340,7 @@ def delete_application( """ self._client.request( method="delete", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), request_options=request_options, ) @@ -370,7 +369,7 @@ def list_application_client_secrets( """ raw = self._client.request_list( method="get", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), request_options=request_options, ) return [ @@ -405,7 +404,7 @@ def create_application_client_secret( body: Dict[str, Any] = {} return self._client.request( method="post", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), body=body, model=NewConnectApplicationSecret, request_options=request_options, @@ -433,7 +432,7 @@ def delete_client_secret( """ self._client.request( method="delete", - path=f"connect/client_secrets/{quote(str(id), safe='')}", + path=("connect", "client_secrets", str(id)), request_options=request_options, ) @@ -497,7 +496,7 @@ async def complete_oauth2( } return await self._client.request( method="post", - path="authkit/oauth2/complete", + path=("authkit", "oauth2", "complete"), body=body, model=ExternalAuthCompleteResponse, request_options=request_options, @@ -547,7 +546,7 @@ async def list_applications( } return await self._client.request_page( method="get", - path="connect/applications", + path=("connect", "applications"), model=ConnectApplication, params=params, request_options=request_options, @@ -580,7 +579,7 @@ async def create_application( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return await self._client.request( method="post", - path="connect/applications", + path=("connect", "applications"), body=_body, model=ConnectApplication, request_options=request_options, @@ -617,7 +616,7 @@ async def create_oauth_application( return await self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -645,7 +644,7 @@ async def create_m2m_application( return await self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -676,7 +675,7 @@ async def get_application( """ return await self._client.request( method="get", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), model=ConnectApplication, request_options=request_options, ) @@ -727,7 +726,7 @@ async def update_application( } return await self._client.request( method="put", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), body=body, model=ConnectApplication, request_options=request_options, @@ -755,7 +754,7 @@ async def delete_application( """ await self._client.request( method="delete", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), request_options=request_options, ) @@ -784,7 +783,7 @@ async def list_application_client_secrets( """ raw = await self._client.request_list( method="get", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), request_options=request_options, ) return [ @@ -819,7 +818,7 @@ async def create_application_client_secret( body: Dict[str, Any] = {} return await self._client.request( method="post", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), body=body, model=NewConnectApplicationSecret, request_options=request_options, @@ -847,6 +846,6 @@ async def delete_client_secret( """ await self._client.request( method="delete", - path=f"connect/client_secrets/{quote(str(id), safe='')}", + path=("connect", "client_secrets", str(id)), request_options=request_options, ) diff --git a/src/workos/directory_sync/_resource.py b/src/workos/directory_sync/_resource.py index 0ab19ca1..10a3bbca 100644 --- a/src/workos/directory_sync/_resource.py +++ b/src/workos/directory_sync/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -72,7 +71,7 @@ def list_directories( } return self._client.request_page( method="get", - path="directories", + path=("directories",), model=Directory, params=params, request_options=request_options, @@ -104,7 +103,7 @@ def get_directory( """ return self._client.request( method="get", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), model=Directory, request_options=request_options, ) @@ -131,7 +130,7 @@ def delete_directory( """ self._client.request( method="delete", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), request_options=request_options, ) @@ -184,7 +183,7 @@ def list_groups( } return self._client.request_page( method="get", - path="directory_groups", + path=("directory_groups",), model=DirectoryGroup, params=params, request_options=request_options, @@ -216,7 +215,7 @@ def get_group( """ return self._client.request( method="get", - path=f"directory_groups/{quote(str(id), safe='')}", + path=("directory_groups", str(id)), model=DirectoryGroup, request_options=request_options, ) @@ -270,7 +269,7 @@ def list_users( } return self._client.request_page( method="get", - path="directory_users", + path=("directory_users",), model=DirectoryUserWithGroups, params=params, request_options=request_options, @@ -302,7 +301,7 @@ def get_user( """ return self._client.request( method="get", - path=f"directory_users/{quote(str(id), safe='')}", + path=("directory_users", str(id)), model=DirectoryUserWithGroups, request_options=request_options, ) @@ -365,7 +364,7 @@ async def list_directories( } return await self._client.request_page( method="get", - path="directories", + path=("directories",), model=Directory, params=params, request_options=request_options, @@ -397,7 +396,7 @@ async def get_directory( """ return await self._client.request( method="get", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), model=Directory, request_options=request_options, ) @@ -424,7 +423,7 @@ async def delete_directory( """ await self._client.request( method="delete", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), request_options=request_options, ) @@ -477,7 +476,7 @@ async def list_groups( } return await self._client.request_page( method="get", - path="directory_groups", + path=("directory_groups",), model=DirectoryGroup, params=params, request_options=request_options, @@ -509,7 +508,7 @@ async def get_group( """ return await self._client.request( method="get", - path=f"directory_groups/{quote(str(id), safe='')}", + path=("directory_groups", str(id)), model=DirectoryGroup, request_options=request_options, ) @@ -563,7 +562,7 @@ async def list_users( } return await self._client.request_page( method="get", - path="directory_users", + path=("directory_users",), model=DirectoryUserWithGroups, params=params, request_options=request_options, @@ -595,7 +594,7 @@ async def get_user( """ return await self._client.request( method="get", - path=f"directory_users/{quote(str(id), safe='')}", + path=("directory_users", str(id)), model=DirectoryUserWithGroups, request_options=request_options, ) diff --git a/src/workos/events/_resource.py b/src/workos/events/_resource.py index d2ef7ffd..cba120e6 100644 --- a/src/workos/events/_resource.py +++ b/src/workos/events/_resource.py @@ -77,7 +77,7 @@ def list_events( SyncPage[EventSchemaVariant], self._client.request_page( method="get", - path="events", + path=("events",), model=EventSchema, # type: ignore[arg-type] # dispatcher; pagination only calls from_dict params=params, request_options=request_options, @@ -149,7 +149,7 @@ async def list_events( AsyncPage[EventSchemaVariant], await self._client.request_page( method="get", - path="events", + path=("events",), model=EventSchema, # type: ignore[arg-type] # dispatcher; pagination only calls from_dict params=params, request_options=request_options, diff --git a/src/workos/feature_flags/_resource.py b/src/workos/feature_flags/_resource.py index 44354d8d..3d232b73 100644 --- a/src/workos/feature_flags/_resource.py +++ b/src/workos/feature_flags/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -64,7 +63,7 @@ def list_feature_flags( } return self._client.request_page( method="get", - path="feature-flags", + path=("feature-flags",), model=Flag, params=params, request_options=request_options, @@ -95,7 +94,7 @@ def get_feature_flag( """ return self._client.request( method="get", - path=f"feature-flags/{quote(str(slug), safe='')}", + path=("feature-flags", str(slug)), model=Flag, request_options=request_options, ) @@ -125,7 +124,7 @@ def disable_feature_flag( """ return self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/disable", + path=("feature-flags", str(slug), "disable"), model=FeatureFlag, request_options=request_options, ) @@ -155,7 +154,7 @@ def enable_feature_flag( """ return self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/enable", + path=("feature-flags", str(slug), "enable"), model=FeatureFlag, request_options=request_options, ) @@ -186,7 +185,7 @@ def add_flag_target( """ self._client.request( method="post", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -216,7 +215,7 @@ def remove_flag_target( """ self._client.request( method="delete", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -263,7 +262,7 @@ def list_organization_feature_flags( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/feature-flags", + path=("organizations", str(organization_id), "feature-flags"), model=Flag, params=params, request_options=request_options, @@ -312,7 +311,7 @@ def list_user_feature_flags( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/feature-flags", + path=("user_management", "users", str(user_id), "feature-flags"), model=Flag, params=params, request_options=request_options, @@ -368,7 +367,7 @@ async def list_feature_flags( } return await self._client.request_page( method="get", - path="feature-flags", + path=("feature-flags",), model=Flag, params=params, request_options=request_options, @@ -399,7 +398,7 @@ async def get_feature_flag( """ return await self._client.request( method="get", - path=f"feature-flags/{quote(str(slug), safe='')}", + path=("feature-flags", str(slug)), model=Flag, request_options=request_options, ) @@ -429,7 +428,7 @@ async def disable_feature_flag( """ return await self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/disable", + path=("feature-flags", str(slug), "disable"), model=FeatureFlag, request_options=request_options, ) @@ -459,7 +458,7 @@ async def enable_feature_flag( """ return await self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/enable", + path=("feature-flags", str(slug), "enable"), model=FeatureFlag, request_options=request_options, ) @@ -490,7 +489,7 @@ async def add_flag_target( """ await self._client.request( method="post", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -520,7 +519,7 @@ async def remove_flag_target( """ await self._client.request( method="delete", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -567,7 +566,7 @@ async def list_organization_feature_flags( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/feature-flags", + path=("organizations", str(organization_id), "feature-flags"), model=Flag, params=params, request_options=request_options, @@ -616,7 +615,7 @@ async def list_user_feature_flags( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/feature-flags", + path=("user_management", "users", str(user_id), "feature-flags"), model=Flag, params=params, request_options=request_options, diff --git a/src/workos/groups/_resource.py b/src/workos/groups/_resource.py index 51c48067..cb86fd26 100644 --- a/src/workos/groups/_resource.py +++ b/src/workos/groups/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -67,7 +66,7 @@ def list_organization_groups( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), model=Group, params=params, request_options=request_options, @@ -113,7 +112,7 @@ def create_organization_group( } return self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), body=body, model=Group, request_options=request_options, @@ -147,7 +146,7 @@ def get_organization_group( """ return self._client.request( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), model=Group, request_options=request_options, ) @@ -194,7 +193,7 @@ def update_organization_group( } return self._client.request( method="patch", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), body=body, model=Group, request_options=request_options, @@ -225,7 +224,7 @@ def delete_organization_group( """ self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), request_options=request_options, ) @@ -275,7 +274,13 @@ def list_group_organization_memberships( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -315,7 +320,13 @@ def create_group_organization_membership( } return self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), body=body, model=Group, request_options=request_options, @@ -348,7 +359,14 @@ def delete_group_organization_membership( """ self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships/{quote(str(om_id), safe='')}", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + str(om_id), + ), request_options=request_options, ) @@ -403,7 +421,7 @@ async def list_organization_groups( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), model=Group, params=params, request_options=request_options, @@ -449,7 +467,7 @@ async def create_organization_group( } return await self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), body=body, model=Group, request_options=request_options, @@ -483,7 +501,7 @@ async def get_organization_group( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), model=Group, request_options=request_options, ) @@ -530,7 +548,7 @@ async def update_organization_group( } return await self._client.request( method="patch", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), body=body, model=Group, request_options=request_options, @@ -561,7 +579,7 @@ async def delete_organization_group( """ await self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), request_options=request_options, ) @@ -611,7 +629,13 @@ async def list_group_organization_memberships( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -651,7 +675,13 @@ async def create_group_organization_membership( } return await self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), body=body, model=Group, request_options=request_options, @@ -684,6 +714,13 @@ async def delete_group_organization_membership( """ await self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships/{quote(str(om_id), safe='')}", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + str(om_id), + ), request_options=request_options, ) diff --git a/src/workos/multi_factor_auth/_resource.py b/src/workos/multi_factor_auth/_resource.py index 6ea2f582..d3f21002 100644 --- a/src/workos/multi_factor_auth/_resource.py +++ b/src/workos/multi_factor_auth/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -63,7 +62,7 @@ def verify_challenge( } return self._client.request( method="post", - path=f"auth/challenges/{quote(str(id), safe='')}/verify", + path=("auth", "challenges", str(id), "verify"), body=body, model=AuthenticationChallengeVerifyResponse, request_options=request_options, @@ -113,7 +112,7 @@ def enroll_factor( } return self._client.request( method="post", - path="auth/factors/enroll", + path=("auth", "factors", "enroll"), body=body, model=AuthenticationFactorEnrolled, request_options=request_options, @@ -144,7 +143,7 @@ def get_factor( """ return self._client.request( method="get", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), model=AuthenticationFactor, request_options=request_options, ) @@ -171,7 +170,7 @@ def delete_factor( """ self._client.request( method="delete", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), request_options=request_options, ) @@ -210,7 +209,7 @@ def challenge_factor( } return self._client.request( method="post", - path=f"auth/factors/{quote(str(id), safe='')}/challenge", + path=("auth", "factors", str(id), "challenge"), body=body, model=AuthenticationChallenge, request_options=request_options, @@ -259,7 +258,7 @@ def list_user_auth_factors( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), model=AuthenticationFactor, params=params, request_options=request_options, @@ -308,7 +307,7 @@ def create_user_auth_factor( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), body=body, model=UserAuthenticationFactorEnrollResponse, request_options=request_options, @@ -353,7 +352,7 @@ async def verify_challenge( } return await self._client.request( method="post", - path=f"auth/challenges/{quote(str(id), safe='')}/verify", + path=("auth", "challenges", str(id), "verify"), body=body, model=AuthenticationChallengeVerifyResponse, request_options=request_options, @@ -403,7 +402,7 @@ async def enroll_factor( } return await self._client.request( method="post", - path="auth/factors/enroll", + path=("auth", "factors", "enroll"), body=body, model=AuthenticationFactorEnrolled, request_options=request_options, @@ -434,7 +433,7 @@ async def get_factor( """ return await self._client.request( method="get", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), model=AuthenticationFactor, request_options=request_options, ) @@ -461,7 +460,7 @@ async def delete_factor( """ await self._client.request( method="delete", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), request_options=request_options, ) @@ -500,7 +499,7 @@ async def challenge_factor( } return await self._client.request( method="post", - path=f"auth/factors/{quote(str(id), safe='')}/challenge", + path=("auth", "factors", str(id), "challenge"), body=body, model=AuthenticationChallenge, request_options=request_options, @@ -549,7 +548,7 @@ async def list_user_auth_factors( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), model=AuthenticationFactor, params=params, request_options=request_options, @@ -598,7 +597,7 @@ async def create_user_auth_factor( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), body=body, model=UserAuthenticationFactorEnrollResponse, request_options=request_options, diff --git a/src/workos/organization_domains/_resource.py b/src/workos/organization_domains/_resource.py index 9c7e5d7a..19d1ba0c 100644 --- a/src/workos/organization_domains/_resource.py +++ b/src/workos/organization_domains/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Optional -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -50,7 +49,7 @@ def create_organization_domain( } return self._client.request( method="post", - path="organization_domains", + path=("organization_domains",), body=body, model=OrganizationDomain, request_options=request_options, @@ -81,7 +80,7 @@ def get_organization_domain( """ return self._client.request( method="get", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), model=OrganizationDomainStandAlone, request_options=request_options, ) @@ -108,7 +107,7 @@ def delete_organization_domain( """ self._client.request( method="delete", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), request_options=request_options, ) @@ -137,7 +136,7 @@ def verify_organization_domain( """ return self._client.request( method="post", - path=f"organization_domains/{quote(str(id), safe='')}/verify", + path=("organization_domains", str(id), "verify"), model=OrganizationDomainStandAlone, request_options=request_options, ) @@ -180,7 +179,7 @@ async def create_organization_domain( } return await self._client.request( method="post", - path="organization_domains", + path=("organization_domains",), body=body, model=OrganizationDomain, request_options=request_options, @@ -211,7 +210,7 @@ async def get_organization_domain( """ return await self._client.request( method="get", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), model=OrganizationDomainStandAlone, request_options=request_options, ) @@ -238,7 +237,7 @@ async def delete_organization_domain( """ await self._client.request( method="delete", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), request_options=request_options, ) @@ -267,7 +266,7 @@ async def verify_organization_domain( """ return await self._client.request( method="post", - path=f"organization_domains/{quote(str(id), safe='')}/verify", + path=("organization_domains", str(id), "verify"), model=OrganizationDomainStandAlone, request_options=request_options, ) diff --git a/src/workos/organizations/_resource.py b/src/workos/organizations/_resource.py index c108ad5a..d91dab3a 100644 --- a/src/workos/organizations/_resource.py +++ b/src/workos/organizations/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -69,7 +68,7 @@ def list_organizations( } return self._client.request_page( method="get", - path="organizations", + path=("organizations",), model=Organization, params=params, request_options=request_options, @@ -126,7 +125,7 @@ def create_organization( } return self._client.request( method="post", - path="organizations", + path=("organizations",), body=body, model=Organization, request_options=request_options, @@ -157,7 +156,7 @@ def get_organization_by_external_id( """ return self._client.request( method="get", - path=f"organizations/external_id/{quote(str(external_id), safe='')}", + path=("organizations", "external_id", str(external_id)), model=Organization, request_options=request_options, ) @@ -187,7 +186,7 @@ def get_organization( """ return self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), model=Organization, request_options=request_options, ) @@ -250,7 +249,7 @@ def update_organization( } return self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), body=body, model=Organization, request_options=request_options, @@ -278,7 +277,7 @@ def delete_organization( """ self._client.request( method="delete", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), request_options=request_options, ) @@ -307,7 +306,7 @@ def get_audit_log_configuration( """ return self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_log_configuration", + path=("organizations", str(id), "audit_log_configuration"), model=AuditLogConfiguration, request_options=request_options, ) @@ -368,7 +367,7 @@ async def list_organizations( } return await self._client.request_page( method="get", - path="organizations", + path=("organizations",), model=Organization, params=params, request_options=request_options, @@ -425,7 +424,7 @@ async def create_organization( } return await self._client.request( method="post", - path="organizations", + path=("organizations",), body=body, model=Organization, request_options=request_options, @@ -456,7 +455,7 @@ async def get_organization_by_external_id( """ return await self._client.request( method="get", - path=f"organizations/external_id/{quote(str(external_id), safe='')}", + path=("organizations", "external_id", str(external_id)), model=Organization, request_options=request_options, ) @@ -486,7 +485,7 @@ async def get_organization( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), model=Organization, request_options=request_options, ) @@ -549,7 +548,7 @@ async def update_organization( } return await self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), body=body, model=Organization, request_options=request_options, @@ -577,7 +576,7 @@ async def delete_organization( """ await self._client.request( method="delete", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), request_options=request_options, ) @@ -606,7 +605,7 @@ async def get_audit_log_configuration( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_log_configuration", + path=("organizations", str(id), "audit_log_configuration"), model=AuditLogConfiguration, request_options=request_options, ) diff --git a/src/workos/passwordless.py b/src/workos/passwordless.py index 12fffb6c..0bd8ee09 100644 --- a/src/workos/passwordless.py +++ b/src/workos/passwordless.py @@ -84,7 +84,7 @@ def create_session( response = self._client.request( method="post", - path="passwordless/sessions", + path=("passwordless", "sessions"), body=body, model=PasswordlessSession, ) @@ -101,7 +101,7 @@ def send_session(self, session_id: str) -> Literal[True]: """ self._client.request( method="post", - path=f"passwordless/sessions/{session_id}/send", + path=("passwordless", "sessions", str(session_id), "send"), ) return True @@ -147,7 +147,7 @@ async def create_session( response = await self._client.request( method="post", - path="passwordless/sessions", + path=("passwordless", "sessions"), body=body, model=PasswordlessSession, ) @@ -164,6 +164,6 @@ async def send_session(self, session_id: str) -> Literal[True]: """ await self._client.request( method="post", - path=f"passwordless/sessions/{session_id}/send", + path=("passwordless", "sessions", str(session_id), "send"), ) return True diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index 9d834e07..9ca384d8 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Optional -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -65,7 +64,7 @@ def authorize_data_integration( } return self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/authorize", + path=("data-integrations", str(slug), "authorize"), body=body, model=DataIntegrationAuthorizeUrlResponse, request_options=request_options, @@ -109,7 +108,7 @@ def create_data_integration_token( } return self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/token", + path=("data-integrations", str(slug), "token"), body=body, model=DataIntegrationAccessTokenResponse, request_options=request_options, @@ -151,7 +150,13 @@ def get_user_connected_account( } return self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, model=ConnectedAccount, request_options=request_options, @@ -190,7 +195,13 @@ def delete_user_connected_account( } self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, request_options=request_options, ) @@ -229,7 +240,7 @@ def list_user_data_providers( } return self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/data_providers", + path=("user_management", "users", str(user_id), "data_providers"), params=params, model=DataIntegrationsListResponse, request_options=request_options, @@ -284,7 +295,7 @@ async def authorize_data_integration( } return await self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/authorize", + path=("data-integrations", str(slug), "authorize"), body=body, model=DataIntegrationAuthorizeUrlResponse, request_options=request_options, @@ -328,7 +339,7 @@ async def create_data_integration_token( } return await self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/token", + path=("data-integrations", str(slug), "token"), body=body, model=DataIntegrationAccessTokenResponse, request_options=request_options, @@ -370,7 +381,13 @@ async def get_user_connected_account( } return await self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, model=ConnectedAccount, request_options=request_options, @@ -409,7 +426,13 @@ async def delete_user_connected_account( } await self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, request_options=request_options, ) @@ -448,7 +471,7 @@ async def list_user_data_providers( } return await self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/data_providers", + path=("user_management", "users", str(user_id), "data_providers"), params=params, model=DataIntegrationsListResponse, request_options=request_options, diff --git a/src/workos/public_client.py b/src/workos/public_client.py index 057b7e40..b4bca338 100644 --- a/src/workos/public_client.py +++ b/src/workos/public_client.py @@ -33,7 +33,9 @@ def create_public_client( from ._client import WorkOSClient return WorkOSClient( + api_key=None, client_id=client_id, base_url=base_url, request_timeout=request_timeout, + is_public=True, ) diff --git a/src/workos/radar/_resource.py b/src/workos/radar/_resource.py index f90d25a4..45c16a58 100644 --- a/src/workos/radar/_resource.py +++ b/src/workos/radar/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -75,7 +74,7 @@ def create_attempt( } return self._client.request( method="post", - path="radar/attempts", + path=("radar", "attempts"), body=body, model=RadarStandaloneResponse, request_options=request_options, @@ -116,7 +115,7 @@ def update_attempt( } self._client.request( method="put", - path=f"radar/attempts/{quote(str(id), safe='')}", + path=("radar", "attempts", str(id)), body=body, request_options=request_options, ) @@ -153,7 +152,7 @@ def add_list_entry( } return self._client.request( method="post", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, model=RadarListEntryAlreadyPresentResponse, request_options=request_options, @@ -189,7 +188,7 @@ def remove_list_entry( } self._client.request( method="delete", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, request_options=request_options, ) @@ -251,7 +250,7 @@ async def create_attempt( } return await self._client.request( method="post", - path="radar/attempts", + path=("radar", "attempts"), body=body, model=RadarStandaloneResponse, request_options=request_options, @@ -292,7 +291,7 @@ async def update_attempt( } await self._client.request( method="put", - path=f"radar/attempts/{quote(str(id), safe='')}", + path=("radar", "attempts", str(id)), body=body, request_options=request_options, ) @@ -329,7 +328,7 @@ async def add_list_entry( } return await self._client.request( method="post", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, model=RadarListEntryAlreadyPresentResponse, request_options=request_options, @@ -365,7 +364,7 @@ async def remove_list_entry( } await self._client.request( method="delete", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, request_options=request_options, ) diff --git a/src/workos/session.py b/src/workos/session.py index fbee198e..330a66bd 100644 --- a/src/workos/session.py +++ b/src/workos/session.py @@ -24,6 +24,20 @@ from cryptography.fernet import Fernet from jwt import PyJWKClient +from ._errors import ( + AuthenticationError, + AuthenticationMethodNotAllowedError, + EmailVerificationRequiredError, + MfaChallengeError, + MfaEnrollmentError, + OrganizationAuthMethodsRequiredError, + OrganizationSelectionRequiredError, + RadarChallengeError, + SsoRequiredError, + WorkOSConnectionError, + WorkOSTimeoutError, +) + if TYPE_CHECKING: from ._client import AsyncWorkOSClient, WorkOSClient @@ -37,6 +51,51 @@ class AuthenticateWithSessionCookieFailureReason(Enum): INVALID_JWT = "invalid_jwt" INVALID_SESSION_COOKIE = "invalid_session_cookie" NO_SESSION_COOKIE_PROVIDED = "no_session_cookie_provided" + MFA_CHALLENGE_REQUIRED = "mfa_challenge_required" + MFA_ENROLLMENT_REQUIRED = "mfa_enrollment_required" + SSO_REQUIRED = "sso_required" + EMAIL_VERIFICATION_REQUIRED = "email_verification_required" + ORGANIZATION_SELECTION_REQUIRED = "organization_selection_required" + ORGANIZATION_AUTH_METHODS_REQUIRED = "organization_auth_methods_required" + AUTHENTICATION_METHOD_NOT_ALLOWED = "authentication_method_not_allowed" + RADAR_CHALLENGE_REQUIRED = "radar_challenge_required" + REFRESH_DENIED = "refresh_denied" + REFRESH_NETWORK_ERROR = "refresh_network_error" + + +def _map_refresh_exception_to_reason( + exc: Exception, +) -> Union[AuthenticateWithSessionCookieFailureReason, str]: + """Map an exception raised by a refresh request to a structured reason. + + Falls back to ``str(exc)`` for unknown errors so callers retain the + pre-existing string form for diagnostics. + """ + if isinstance(exc, MfaChallengeError): + return AuthenticateWithSessionCookieFailureReason.MFA_CHALLENGE_REQUIRED + if isinstance(exc, MfaEnrollmentError): + return AuthenticateWithSessionCookieFailureReason.MFA_ENROLLMENT_REQUIRED + if isinstance(exc, SsoRequiredError): + return AuthenticateWithSessionCookieFailureReason.SSO_REQUIRED + if isinstance(exc, EmailVerificationRequiredError): + return AuthenticateWithSessionCookieFailureReason.EMAIL_VERIFICATION_REQUIRED + if isinstance(exc, OrganizationSelectionRequiredError): + return ( + AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED + ) + if isinstance(exc, OrganizationAuthMethodsRequiredError): + return AuthenticateWithSessionCookieFailureReason.ORGANIZATION_AUTH_METHODS_REQUIRED + if isinstance(exc, AuthenticationMethodNotAllowedError): + return ( + AuthenticateWithSessionCookieFailureReason.AUTHENTICATION_METHOD_NOT_ALLOWED + ) + if isinstance(exc, RadarChallengeError): + return AuthenticateWithSessionCookieFailureReason.RADAR_CHALLENGE_REQUIRED + if isinstance(exc, AuthenticationError): + return AuthenticateWithSessionCookieFailureReason.REFRESH_DENIED + if isinstance(exc, (WorkOSConnectionError, WorkOSTimeoutError)): + return AuthenticateWithSessionCookieFailureReason.REFRESH_NETWORK_ERROR + return str(exc) @dataclass(slots=True) @@ -295,7 +354,7 @@ def refresh( auth_response = self._client.request_raw( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, ) @@ -328,7 +387,7 @@ def refresh( ) except Exception as e: return RefreshWithSessionCookieErrorResponse( - authenticated=False, reason=str(e) + authenticated=False, reason=_map_refresh_exception_to_reason(e) ) def get_logout_url(self, return_to: Optional[str] = None) -> str: @@ -474,7 +533,7 @@ async def refresh( auth_response = await self._client.request_raw( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, ) @@ -507,7 +566,7 @@ async def refresh( ) except Exception as e: return RefreshWithSessionCookieErrorResponse( - authenticated=False, reason=str(e) + authenticated=False, reason=_map_refresh_exception_to_reason(e) ) async def get_logout_url(self, return_to: Optional[str] = None) -> str: diff --git a/src/workos/sso/_resource.py b/src/workos/sso/_resource.py index d4e0a9ee..c088011a 100644 --- a/src/workos/sso/_resource.py +++ b/src/workos/sso/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -77,7 +76,7 @@ def list_connections( } return self._client.request_page( method="get", - path="connections", + path=("connections",), model=Connection, params=params, request_options=request_options, @@ -109,7 +108,7 @@ def get_connection( """ return self._client.request( method="get", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), model=Connection, request_options=request_options, ) @@ -137,7 +136,7 @@ def delete_connection( """ self._client.request( method="delete", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), request_options=request_options, ) @@ -207,7 +206,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("sso/authorize", params) + return self._client.build_url(("sso", "authorize"), params) def get_logout_url( self, @@ -241,7 +240,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("sso/logout", params) + return self._client.build_url(("sso", "logout"), params) def authorize_logout( self, @@ -272,7 +271,7 @@ def authorize_logout( } return self._client.request( method="post", - path="sso/logout/authorize", + path=("sso", "logout", "authorize"), body=body, model=SSOLogoutAuthorizeResponse, request_options=request_options, @@ -311,7 +310,7 @@ def get_profile( } return self._client.request( method="get", - path="sso/profile", + path=("sso", "profile"), model=Profile, request_options=request_options, ) @@ -351,7 +350,7 @@ def get_profile_and_token( body["client_secret"] = self._client._api_key return self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, @@ -399,7 +398,7 @@ def get_authorization_url_with_pkce( }.items() if v is not None } - url = self._client.build_url("sso/authorize", params) + url = self._client.build_url(("sso", "authorize"), params) return {"url": url, "state": state, "code_verifier": pair.code_verifier} def get_profile_and_token_pkce( @@ -423,7 +422,7 @@ def get_profile_and_token_pkce( return self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, @@ -494,7 +493,7 @@ async def list_connections( } return await self._client.request_page( method="get", - path="connections", + path=("connections",), model=Connection, params=params, request_options=request_options, @@ -526,7 +525,7 @@ async def get_connection( """ return await self._client.request( method="get", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), model=Connection, request_options=request_options, ) @@ -554,7 +553,7 @@ async def delete_connection( """ await self._client.request( method="delete", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), request_options=request_options, ) @@ -624,7 +623,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("sso/authorize", params) + return self._client.build_url(("sso", "authorize"), params) def get_logout_url( self, @@ -658,7 +657,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("sso/logout", params) + return self._client.build_url(("sso", "logout"), params) async def authorize_logout( self, @@ -689,7 +688,7 @@ async def authorize_logout( } return await self._client.request( method="post", - path="sso/logout/authorize", + path=("sso", "logout", "authorize"), body=body, model=SSOLogoutAuthorizeResponse, request_options=request_options, @@ -728,7 +727,7 @@ async def get_profile( } return await self._client.request( method="get", - path="sso/profile", + path=("sso", "profile"), model=Profile, request_options=request_options, ) @@ -768,7 +767,7 @@ async def get_profile_and_token( body["client_secret"] = self._client._api_key return await self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, @@ -816,7 +815,7 @@ async def get_authorization_url_with_pkce( }.items() if v is not None } - url = self._client.build_url("sso/authorize", params) + url = self._client.build_url(("sso", "authorize"), params) return {"url": url, "state": state, "code_verifier": pair.code_verifier} async def get_profile_and_token_pkce( @@ -840,7 +839,7 @@ async def get_profile_and_token_pkce( return await self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, diff --git a/src/workos/user_management/_resource.py b/src/workos/user_management/_resource.py index 12e34b52..c9fbd8fb 100644 --- a/src/workos/user_management/_resource.py +++ b/src/workos/user_management/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -128,7 +127,7 @@ def get_jwks( """ return self._client.request( method="get", - path=f"sso/jwks/{quote(str(client_id), safe='')}", + path=("sso", "jwks", str(client_id)), model=JwksResponse, request_options=request_options, ) @@ -172,7 +171,7 @@ def create_authenticate( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return self._client.request( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=_body, model=AuthenticateResponse, request_options=request_options, @@ -210,7 +209,7 @@ def authenticate_with_password( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -249,7 +248,7 @@ def authenticate_with_code( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -285,7 +284,7 @@ def authenticate_with_refresh_token( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -323,7 +322,7 @@ def authenticate_with_magic_auth( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -358,7 +357,7 @@ def authenticate_with_email_verification( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -395,7 +394,7 @@ def authenticate_with_totp( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -430,7 +429,7 @@ def authenticate_with_organization_selection( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -461,7 +460,7 @@ def authenticate_with_device_code( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -544,7 +543,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("user_management/authorize", params) + return self._client.build_url(("user_management", "authorize"), params) def create_device( self, @@ -575,7 +574,7 @@ def create_device( } return self._client.request( method="post", - path="user_management/authorize/device", + path=("user_management", "authorize", "device"), body=body, model=DeviceAuthorizationResponse, request_options=request_options, @@ -614,7 +613,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("user_management/sessions/logout", params) + return self._client.build_url(("user_management", "sessions", "logout"), params) def revoke_session( self, @@ -648,7 +647,7 @@ def revoke_session( } self._client.request( method="post", - path="user_management/sessions/revoke", + path=("user_management", "sessions", "revoke"), body=body, request_options=request_options, ) @@ -682,7 +681,7 @@ def create_cors_origin( } return self._client.request( method="post", - path="user_management/cors_origins", + path=("user_management", "cors_origins"), body=body, model=CORSOriginResponse, request_options=request_options, @@ -713,7 +712,7 @@ def get_email_verification( """ return self._client.request( method="get", - path=f"user_management/email_verification/{quote(str(id), safe='')}", + path=("user_management", "email_verification", str(id)), model=EmailVerification, request_options=request_options, ) @@ -748,7 +747,7 @@ def reset_password( } return self._client.request( method="post", - path="user_management/password_reset", + path=("user_management", "password_reset"), body=body, model=PasswordReset, request_options=request_options, @@ -788,7 +787,7 @@ def confirm_password_reset( } return self._client.request( method="post", - path="user_management/password_reset/confirm", + path=("user_management", "password_reset", "confirm"), body=body, model=ResetPasswordResponse, request_options=request_options, @@ -819,7 +818,7 @@ def get_password_reset( """ return self._client.request( method="get", - path=f"user_management/password_reset/{quote(str(id), safe='')}", + path=("user_management", "password_reset", str(id)), model=PasswordReset, request_options=request_options, ) @@ -874,7 +873,7 @@ def list_users( } return self._client.request_page( method="get", - path="user_management/users", + path=("user_management", "users"), model=User, params=params, request_options=request_options, @@ -937,7 +936,7 @@ def create_user( body["password_hash_type"] = enum_value(password.password_hash_type) return self._client.request( method="post", - path="user_management/users", + path=("user_management", "users"), body=body, model=User, request_options=request_options, @@ -968,7 +967,7 @@ def get_user_by_external_id( """ return self._client.request( method="get", - path=f"user_management/users/external_id/{quote(str(external_id), safe='')}", + path=("user_management", "users", "external_id", str(external_id)), model=User, request_options=request_options, ) @@ -998,7 +997,7 @@ def get_user( """ return self._client.request( method="get", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), model=User, request_options=request_options, ) @@ -1064,7 +1063,7 @@ def update_user( body["password_hash_type"] = enum_value(password.password_hash_type) return self._client.request( method="put", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), body=body, model=User, request_options=request_options, @@ -1092,7 +1091,7 @@ def delete_user( """ self._client.request( method="delete", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), request_options=request_options, ) @@ -1129,7 +1128,7 @@ def confirm_email_change( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/confirm", + path=("user_management", "users", str(id), "email_change", "confirm"), body=body, model=EmailChangeConfirmation, request_options=request_options, @@ -1167,7 +1166,7 @@ def send_email_change( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/send", + path=("user_management", "users", str(id), "email_change", "send"), body=body, model=EmailChange, request_options=request_options, @@ -1205,7 +1204,7 @@ def verify_email( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/confirm", + path=("user_management", "users", str(id), "email_verification", "confirm"), body=body, model=VerifyEmailResponse, request_options=request_options, @@ -1237,7 +1236,7 @@ def send_verification_email( """ return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/send", + path=("user_management", "users", str(id), "email_verification", "send"), model=SendVerificationEmailResponse, request_options=request_options, ) @@ -1267,7 +1266,7 @@ def get_user_identities( """ raw = self._client.request_list( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/identities", + path=("user_management", "users", str(id), "identities"), request_options=request_options, ) return [ @@ -1318,7 +1317,7 @@ def list_sessions( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/sessions", + path=("user_management", "users", str(id), "sessions"), model=UserSessionsListItem, params=params, request_options=request_options, @@ -1371,7 +1370,7 @@ def list_invitations( } return self._client.request_page( method="get", - path="user_management/invitations", + path=("user_management", "invitations"), model=UserInvite, params=params, request_options=request_options, @@ -1426,7 +1425,7 @@ def send_invitation( } return self._client.request( method="post", - path="user_management/invitations", + path=("user_management", "invitations"), body=body, model=UserInvite, request_options=request_options, @@ -1457,7 +1456,7 @@ def find_invitation_by_token( """ return self._client.request( method="get", - path=f"user_management/invitations/by_token/{quote(str(token), safe='')}", + path=("user_management", "invitations", "by_token", str(token)), model=UserInvite, request_options=request_options, ) @@ -1487,7 +1486,7 @@ def get_invitation( """ return self._client.request( method="get", - path=f"user_management/invitations/{quote(str(id), safe='')}", + path=("user_management", "invitations", str(id)), model=UserInvite, request_options=request_options, ) @@ -1518,7 +1517,7 @@ def accept_invitation( """ return self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/accept", + path=("user_management", "invitations", str(id), "accept"), model=Invitation, request_options=request_options, ) @@ -1559,7 +1558,7 @@ def resend_invitation( } return self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/resend", + path=("user_management", "invitations", str(id), "resend"), body=body, model=UserInvite, request_options=request_options, @@ -1590,7 +1589,7 @@ def revoke_invitation( """ return self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/revoke", + path=("user_management", "invitations", str(id), "revoke"), model=Invitation, request_options=request_options, ) @@ -1615,7 +1614,7 @@ def list_jwt_template( """ return self._client.request( method="get", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), model=JWTTemplateResponse, request_options=request_options, ) @@ -1648,7 +1647,7 @@ def update_jwt_template( } return self._client.request( method="put", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), body=body, model=JWTTemplateResponse, request_options=request_options, @@ -1690,7 +1689,7 @@ def create_magic_auth( } return self._client.request( method="post", - path="user_management/magic_auth", + path=("user_management", "magic_auth"), body=body, model=MagicAuth, request_options=request_options, @@ -1721,7 +1720,7 @@ def get_magic_auth( """ return self._client.request( method="get", - path=f"user_management/magic_auth/{quote(str(id), safe='')}", + path=("user_management", "magic_auth", str(id)), model=MagicAuth, request_options=request_options, ) @@ -1782,7 +1781,7 @@ def list_organization_memberships( } return self._client.request_page( method="get", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), model=UserOrganizationMembership, params=params, request_options=request_options, @@ -1830,7 +1829,7 @@ def create_organization_membership( body["role_slugs"] = role.role_slugs return self._client.request( method="post", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), body=body, model=OrganizationMembership, request_options=request_options, @@ -1861,7 +1860,7 @@ def get_organization_membership( """ return self._client.request( method="get", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), model=UserOrganizationMembership, request_options=request_options, ) @@ -1900,7 +1899,7 @@ def update_organization_membership( body["role_slugs"] = role.role_slugs return self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), body=body, model=UserOrganizationMembership, request_options=request_options, @@ -1928,7 +1927,7 @@ def delete_organization_membership( """ self._client.request( method="delete", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), request_options=request_options, ) @@ -1964,7 +1963,7 @@ def deactivate_organization_membership( """ return self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/deactivate", + path=("user_management", "organization_memberships", str(id), "deactivate"), model=OrganizationMembership, request_options=request_options, ) @@ -2001,7 +2000,7 @@ def reactivate_organization_membership( """ return self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/reactivate", + path=("user_management", "organization_memberships", str(id), "reactivate"), model=UserOrganizationMembership, request_options=request_options, ) @@ -2035,7 +2034,7 @@ def create_redirect_uri( } return self._client.request( method="post", - path="user_management/redirect_uris", + path=("user_management", "redirect_uris"), body=body, model=RedirectUri, request_options=request_options, @@ -2085,7 +2084,7 @@ def list_user_authorized_applications( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications", + path=("user_management", "users", str(user_id), "authorized_applications"), model=AuthorizedConnectApplicationListData, params=params, request_options=request_options, @@ -2115,7 +2114,13 @@ def delete_user_authorized_application( """ self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications/{quote(str(application_id), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "authorized_applications", + str(application_id), + ), request_options=request_options, ) @@ -2165,7 +2170,7 @@ def list_user_api_keys( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), model=UserApiKey, params=params, request_options=request_options, @@ -2213,7 +2218,7 @@ def create_user_api_key( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), body=body, model=UserApiKeyWithValue, request_options=request_options, @@ -2395,7 +2400,7 @@ def authenticate_with_code_pkce( AuthenticateResponse, self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2436,7 +2441,7 @@ async def get_jwks( """ return await self._client.request( method="get", - path=f"sso/jwks/{quote(str(client_id), safe='')}", + path=("sso", "jwks", str(client_id)), model=JwksResponse, request_options=request_options, ) @@ -2480,7 +2485,7 @@ async def create_authenticate( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return await self._client.request( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=_body, model=AuthenticateResponse, request_options=request_options, @@ -2518,7 +2523,7 @@ async def authenticate_with_password( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2557,7 +2562,7 @@ async def authenticate_with_code( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2593,7 +2598,7 @@ async def authenticate_with_refresh_token( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2631,7 +2636,7 @@ async def authenticate_with_magic_auth( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2666,7 +2671,7 @@ async def authenticate_with_email_verification( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2703,7 +2708,7 @@ async def authenticate_with_totp( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2738,7 +2743,7 @@ async def authenticate_with_organization_selection( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2769,7 +2774,7 @@ async def authenticate_with_device_code( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2852,7 +2857,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("user_management/authorize", params) + return self._client.build_url(("user_management", "authorize"), params) async def create_device( self, @@ -2883,7 +2888,7 @@ async def create_device( } return await self._client.request( method="post", - path="user_management/authorize/device", + path=("user_management", "authorize", "device"), body=body, model=DeviceAuthorizationResponse, request_options=request_options, @@ -2922,7 +2927,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("user_management/sessions/logout", params) + return self._client.build_url(("user_management", "sessions", "logout"), params) async def revoke_session( self, @@ -2956,7 +2961,7 @@ async def revoke_session( } await self._client.request( method="post", - path="user_management/sessions/revoke", + path=("user_management", "sessions", "revoke"), body=body, request_options=request_options, ) @@ -2990,7 +2995,7 @@ async def create_cors_origin( } return await self._client.request( method="post", - path="user_management/cors_origins", + path=("user_management", "cors_origins"), body=body, model=CORSOriginResponse, request_options=request_options, @@ -3021,7 +3026,7 @@ async def get_email_verification( """ return await self._client.request( method="get", - path=f"user_management/email_verification/{quote(str(id), safe='')}", + path=("user_management", "email_verification", str(id)), model=EmailVerification, request_options=request_options, ) @@ -3056,7 +3061,7 @@ async def reset_password( } return await self._client.request( method="post", - path="user_management/password_reset", + path=("user_management", "password_reset"), body=body, model=PasswordReset, request_options=request_options, @@ -3096,7 +3101,7 @@ async def confirm_password_reset( } return await self._client.request( method="post", - path="user_management/password_reset/confirm", + path=("user_management", "password_reset", "confirm"), body=body, model=ResetPasswordResponse, request_options=request_options, @@ -3127,7 +3132,7 @@ async def get_password_reset( """ return await self._client.request( method="get", - path=f"user_management/password_reset/{quote(str(id), safe='')}", + path=("user_management", "password_reset", str(id)), model=PasswordReset, request_options=request_options, ) @@ -3182,7 +3187,7 @@ async def list_users( } return await self._client.request_page( method="get", - path="user_management/users", + path=("user_management", "users"), model=User, params=params, request_options=request_options, @@ -3245,7 +3250,7 @@ async def create_user( body["password_hash_type"] = enum_value(password.password_hash_type) return await self._client.request( method="post", - path="user_management/users", + path=("user_management", "users"), body=body, model=User, request_options=request_options, @@ -3276,7 +3281,7 @@ async def get_user_by_external_id( """ return await self._client.request( method="get", - path=f"user_management/users/external_id/{quote(str(external_id), safe='')}", + path=("user_management", "users", "external_id", str(external_id)), model=User, request_options=request_options, ) @@ -3306,7 +3311,7 @@ async def get_user( """ return await self._client.request( method="get", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), model=User, request_options=request_options, ) @@ -3372,7 +3377,7 @@ async def update_user( body["password_hash_type"] = enum_value(password.password_hash_type) return await self._client.request( method="put", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), body=body, model=User, request_options=request_options, @@ -3400,7 +3405,7 @@ async def delete_user( """ await self._client.request( method="delete", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), request_options=request_options, ) @@ -3437,7 +3442,7 @@ async def confirm_email_change( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/confirm", + path=("user_management", "users", str(id), "email_change", "confirm"), body=body, model=EmailChangeConfirmation, request_options=request_options, @@ -3475,7 +3480,7 @@ async def send_email_change( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/send", + path=("user_management", "users", str(id), "email_change", "send"), body=body, model=EmailChange, request_options=request_options, @@ -3513,7 +3518,7 @@ async def verify_email( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/confirm", + path=("user_management", "users", str(id), "email_verification", "confirm"), body=body, model=VerifyEmailResponse, request_options=request_options, @@ -3545,7 +3550,7 @@ async def send_verification_email( """ return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/send", + path=("user_management", "users", str(id), "email_verification", "send"), model=SendVerificationEmailResponse, request_options=request_options, ) @@ -3575,7 +3580,7 @@ async def get_user_identities( """ raw = await self._client.request_list( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/identities", + path=("user_management", "users", str(id), "identities"), request_options=request_options, ) return [ @@ -3626,7 +3631,7 @@ async def list_sessions( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/sessions", + path=("user_management", "users", str(id), "sessions"), model=UserSessionsListItem, params=params, request_options=request_options, @@ -3679,7 +3684,7 @@ async def list_invitations( } return await self._client.request_page( method="get", - path="user_management/invitations", + path=("user_management", "invitations"), model=UserInvite, params=params, request_options=request_options, @@ -3734,7 +3739,7 @@ async def send_invitation( } return await self._client.request( method="post", - path="user_management/invitations", + path=("user_management", "invitations"), body=body, model=UserInvite, request_options=request_options, @@ -3765,7 +3770,7 @@ async def find_invitation_by_token( """ return await self._client.request( method="get", - path=f"user_management/invitations/by_token/{quote(str(token), safe='')}", + path=("user_management", "invitations", "by_token", str(token)), model=UserInvite, request_options=request_options, ) @@ -3795,7 +3800,7 @@ async def get_invitation( """ return await self._client.request( method="get", - path=f"user_management/invitations/{quote(str(id), safe='')}", + path=("user_management", "invitations", str(id)), model=UserInvite, request_options=request_options, ) @@ -3826,7 +3831,7 @@ async def accept_invitation( """ return await self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/accept", + path=("user_management", "invitations", str(id), "accept"), model=Invitation, request_options=request_options, ) @@ -3867,7 +3872,7 @@ async def resend_invitation( } return await self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/resend", + path=("user_management", "invitations", str(id), "resend"), body=body, model=UserInvite, request_options=request_options, @@ -3898,7 +3903,7 @@ async def revoke_invitation( """ return await self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/revoke", + path=("user_management", "invitations", str(id), "revoke"), model=Invitation, request_options=request_options, ) @@ -3923,7 +3928,7 @@ async def list_jwt_template( """ return await self._client.request( method="get", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), model=JWTTemplateResponse, request_options=request_options, ) @@ -3956,7 +3961,7 @@ async def update_jwt_template( } return await self._client.request( method="put", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), body=body, model=JWTTemplateResponse, request_options=request_options, @@ -3998,7 +4003,7 @@ async def create_magic_auth( } return await self._client.request( method="post", - path="user_management/magic_auth", + path=("user_management", "magic_auth"), body=body, model=MagicAuth, request_options=request_options, @@ -4029,7 +4034,7 @@ async def get_magic_auth( """ return await self._client.request( method="get", - path=f"user_management/magic_auth/{quote(str(id), safe='')}", + path=("user_management", "magic_auth", str(id)), model=MagicAuth, request_options=request_options, ) @@ -4090,7 +4095,7 @@ async def list_organization_memberships( } return await self._client.request_page( method="get", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), model=UserOrganizationMembership, params=params, request_options=request_options, @@ -4138,7 +4143,7 @@ async def create_organization_membership( body["role_slugs"] = role.role_slugs return await self._client.request( method="post", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), body=body, model=OrganizationMembership, request_options=request_options, @@ -4169,7 +4174,7 @@ async def get_organization_membership( """ return await self._client.request( method="get", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), model=UserOrganizationMembership, request_options=request_options, ) @@ -4208,7 +4213,7 @@ async def update_organization_membership( body["role_slugs"] = role.role_slugs return await self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), body=body, model=UserOrganizationMembership, request_options=request_options, @@ -4236,7 +4241,7 @@ async def delete_organization_membership( """ await self._client.request( method="delete", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), request_options=request_options, ) @@ -4272,7 +4277,7 @@ async def deactivate_organization_membership( """ return await self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/deactivate", + path=("user_management", "organization_memberships", str(id), "deactivate"), model=OrganizationMembership, request_options=request_options, ) @@ -4309,7 +4314,7 @@ async def reactivate_organization_membership( """ return await self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/reactivate", + path=("user_management", "organization_memberships", str(id), "reactivate"), model=UserOrganizationMembership, request_options=request_options, ) @@ -4343,7 +4348,7 @@ async def create_redirect_uri( } return await self._client.request( method="post", - path="user_management/redirect_uris", + path=("user_management", "redirect_uris"), body=body, model=RedirectUri, request_options=request_options, @@ -4393,7 +4398,7 @@ async def list_user_authorized_applications( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications", + path=("user_management", "users", str(user_id), "authorized_applications"), model=AuthorizedConnectApplicationListData, params=params, request_options=request_options, @@ -4423,7 +4428,13 @@ async def delete_user_authorized_application( """ await self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications/{quote(str(application_id), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "authorized_applications", + str(application_id), + ), request_options=request_options, ) @@ -4473,7 +4484,7 @@ async def list_user_api_keys( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), model=UserApiKey, params=params, request_options=request_options, @@ -4521,7 +4532,7 @@ async def create_user_api_key( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), body=body, model=UserApiKeyWithValue, request_options=request_options, @@ -4703,7 +4714,7 @@ async def authenticate_with_code_pkce( AuthenticateResponse, await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, diff --git a/src/workos/user_management_organization_membership_groups/_resource.py b/src/workos/user_management_organization_membership_groups/_resource.py index 5b04421e..48fd4159 100644 --- a/src/workos/user_management_organization_membership_groups/_resource.py +++ b/src/workos/user_management_organization_membership_groups/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -63,7 +62,7 @@ def list_organization_membership_groups( } return self._client.request_page( method="get", - path=f"user_management/organization_memberships/{quote(str(om_id), safe='')}/groups", + path=("user_management", "organization_memberships", str(om_id), "groups"), model=Group, params=params, request_options=request_options, @@ -119,7 +118,7 @@ async def list_organization_membership_groups( } return await self._client.request_page( method="get", - path=f"user_management/organization_memberships/{quote(str(om_id), safe='')}/groups", + path=("user_management", "organization_memberships", str(om_id), "groups"), model=Group, params=params, request_options=request_options, diff --git a/src/workos/vault.py b/src/workos/vault.py index b5d50895..77f4058f 100644 --- a/src/workos/vault.py +++ b/src/workos/vault.py @@ -282,10 +282,12 @@ def _decode_u32_leb128(buf: bytes) -> Tuple[int, int]: res = 0 bit = 0 for i, b in enumerate(buf): - if i > 4: + if i >= 4 and (b & 0x80) != 0: raise ValueError("LEB128 integer overflow (was more than 4 bytes)") res |= (b & 0x7F) << (7 * bit) if (b & 0x80) == 0: + if res > 0xFFFFFFFF: + raise ValueError("LEB128 integer overflow (exceeds 32 bits)") return res, i + 1 bit += 1 raise ValueError("LEB128 integer not found") @@ -332,7 +334,7 @@ def read_object(self, *, object_id: str) -> VaultObject: """Get a Vault object with the value decrypted.""" response = self._client.request( method="get", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), model=VaultObject, ) return response @@ -341,7 +343,7 @@ def read_object_by_name(self, *, name: str) -> VaultObject: """Get a Vault object by name with the value decrypted.""" response = self._client.request( method="get", - path=f"vault/v1/kv/name/{name}", + path=("vault", "v1", "kv", "name", str(name)), model=VaultObject, ) return response @@ -350,7 +352,7 @@ def get_object_metadata(self, *, object_id: str) -> VaultObject: """Get a Vault object's metadata without decrypting the value.""" response = self._client.request( method="get", - path=f"vault/v1/kv/{object_id}/metadata", + path=("vault", "v1", "kv", str(object_id), "metadata"), model=VaultObject, ) return response @@ -371,7 +373,7 @@ def list_objects( response = self._client.request_raw( method="get", - path="vault/v1/kv", + path=("vault", "v1", "kv"), params=params, ) data: List[Dict[str, Any]] = response.get("data", []) @@ -381,7 +383,7 @@ def list_object_versions(self, *, object_id: str) -> Sequence[ObjectVersion]: """Gets a list of versions for a specific Vault object.""" response = self._client.request_raw( method="get", - path=f"vault/v1/kv/{object_id}/versions", + path=("vault", "v1", "kv", str(object_id), "versions"), ) data: List[Dict[str, Any]] = response.get("data", []) return [ObjectVersion.from_dict(v) for v in data] @@ -396,7 +398,7 @@ def create_object( """Create a new Vault encrypted object.""" response = self._client.request( method="post", - path="vault/v1/kv", + path=("vault", "v1", "kv"), body={"name": name, "value": value, "key_context": key_context}, model=ObjectMetadata, ) @@ -416,7 +418,7 @@ def update_object( response = self._client.request( method="put", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), body=body, model=VaultObject, ) @@ -426,7 +428,7 @@ def delete_object(self, *, object_id: str) -> None: """Permanently delete a Vault encrypted object.""" self._client.request( method="delete", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), ) # -- Key operations -- @@ -435,7 +437,7 @@ def create_data_key(self, *, key_context: KeyContext) -> DataKeyPair: """Generate a data key for local encryption.""" response = self._client.request_raw( method="post", - path="vault/v1/keys/data-key", + path=("vault", "v1", "keys", "data-key"), body={"context": key_context}, ) return DataKeyPair( @@ -448,7 +450,7 @@ def decrypt_data_key(self, *, keys: str) -> DataKey: """Decrypt encrypted data keys previously generated by create_data_key.""" response = self._client.request_raw( method="post", - path="vault/v1/keys/decrypt", + path=("vault", "v1", "keys", "decrypt"), body={"keys": keys}, ) return DataKey(id=response["id"], key=response["data_key"]) @@ -517,7 +519,7 @@ async def read_object(self, *, object_id: str) -> VaultObject: """Get a Vault object with the value decrypted.""" response = await self._client.request( method="get", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), model=VaultObject, ) return response @@ -526,7 +528,7 @@ async def read_object_by_name(self, *, name: str) -> VaultObject: """Get a Vault object by name with the value decrypted.""" response = await self._client.request( method="get", - path=f"vault/v1/kv/name/{name}", + path=("vault", "v1", "kv", "name", str(name)), model=VaultObject, ) return response @@ -535,7 +537,7 @@ async def get_object_metadata(self, *, object_id: str) -> VaultObject: """Get a Vault object's metadata without decrypting the value.""" response = await self._client.request( method="get", - path=f"vault/v1/kv/{object_id}/metadata", + path=("vault", "v1", "kv", str(object_id), "metadata"), model=VaultObject, ) return response @@ -555,7 +557,7 @@ async def list_objects( params["after"] = after response = await self._client.request_raw( method="get", - path="vault/v1/kv", + path=("vault", "v1", "kv"), params=params, ) data: List[Dict[str, Any]] = response.get("data", []) @@ -565,7 +567,7 @@ async def list_object_versions(self, *, object_id: str) -> Sequence[ObjectVersio """Gets a list of versions for a specific Vault object.""" response = await self._client.request_raw( method="get", - path=f"vault/v1/kv/{object_id}/versions", + path=("vault", "v1", "kv", str(object_id), "versions"), ) data: List[Dict[str, Any]] = response.get("data", []) return [ObjectVersion.from_dict(v) for v in data] @@ -580,7 +582,7 @@ async def create_object( """Create a new Vault encrypted object.""" response = await self._client.request( method="post", - path="vault/v1/kv", + path=("vault", "v1", "kv"), body={"name": name, "value": value, "key_context": key_context}, model=ObjectMetadata, ) @@ -599,7 +601,7 @@ async def update_object( body["version_check"] = version_check response = await self._client.request( method="put", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), body=body, model=VaultObject, ) @@ -609,14 +611,14 @@ async def delete_object(self, *, object_id: str) -> None: """Permanently delete a Vault encrypted object.""" await self._client.request( method="delete", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), ) async def create_data_key(self, *, key_context: KeyContext) -> DataKeyPair: """Generate a data key for local encryption.""" response = await self._client.request_raw( method="post", - path="vault/v1/keys/data-key", + path=("vault", "v1", "keys", "data-key"), body={"context": key_context}, ) return DataKeyPair( @@ -629,7 +631,7 @@ async def decrypt_data_key(self, *, keys: str) -> DataKey: """Decrypt encrypted data keys previously generated by create_data_key.""" response = await self._client.request_raw( method="post", - path="vault/v1/keys/decrypt", + path=("vault", "v1", "keys", "decrypt"), body={"keys": keys}, ) return DataKey(id=response["id"], key=response["data_key"]) diff --git a/src/workos/webhooks/_resource.py b/src/workos/webhooks/_resource.py index 0b0d11fb..652f051e 100644 --- a/src/workos/webhooks/_resource.py +++ b/src/workos/webhooks/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -73,7 +72,7 @@ def list_webhook_endpoints( } return self._client.request_page( method="get", - path="webhook_endpoints", + path=("webhook_endpoints",), model=WebhookEndpointJson, params=params, request_options=request_options, @@ -111,7 +110,7 @@ def create_webhook_endpoint( } return self._client.request( method="post", - path="webhook_endpoints", + path=("webhook_endpoints",), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -159,7 +158,7 @@ def update_webhook_endpoint( } return self._client.request( method="patch", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -187,7 +186,7 @@ def delete_webhook_endpoint( """ self._client.request( method="delete", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), request_options=request_options, ) @@ -263,7 +262,7 @@ def verify_header( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > max_seconds_since_issued: + if abs(seconds_since_issued) > max_seconds_since_issued: raise ValueError("Timestamp outside the tolerance zone") body_str = ( @@ -330,7 +329,7 @@ async def list_webhook_endpoints( } return await self._client.request_page( method="get", - path="webhook_endpoints", + path=("webhook_endpoints",), model=WebhookEndpointJson, params=params, request_options=request_options, @@ -368,7 +367,7 @@ async def create_webhook_endpoint( } return await self._client.request( method="post", - path="webhook_endpoints", + path=("webhook_endpoints",), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -416,7 +415,7 @@ async def update_webhook_endpoint( } return await self._client.request( method="patch", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -444,7 +443,7 @@ async def delete_webhook_endpoint( """ await self._client.request( method="delete", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), request_options=request_options, ) @@ -520,7 +519,7 @@ def verify_header( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > max_seconds_since_issued: + if abs(seconds_since_issued) > max_seconds_since_issued: raise ValueError("Timestamp outside the tolerance zone") body_str = ( diff --git a/src/workos/webhooks/_verification.py b/src/workos/webhooks/_verification.py index 3dc99582..3ec82bef 100644 --- a/src/workos/webhooks/_verification.py +++ b/src/workos/webhooks/_verification.py @@ -78,12 +78,12 @@ def verify_header( issued_timestamp = issued_timestamp[2:] signature_hash = signature_hash[3:] - max_seconds_since_issued = tolerance or DEFAULT_TOLERANCE + max_seconds_since_issued = tolerance if tolerance is not None else DEFAULT_TOLERANCE current_time = time.time() timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > max_seconds_since_issued: + if abs(seconds_since_issued) > max_seconds_since_issued: raise ValueError("Timestamp outside the tolerance zone") unhashed_string = "{0}.{1}".format(issued_timestamp, event_body.decode("utf-8")) diff --git a/src/workos/widgets/_resource.py b/src/workos/widgets/_resource.py index 8d9e491d..0834e1ea 100644 --- a/src/workos/widgets/_resource.py +++ b/src/workos/widgets/_resource.py @@ -58,7 +58,7 @@ def create_token( } return self._client.request( method="post", - path="widgets/token", + path=("widgets", "token"), body=body, model=WidgetSessionTokenResponse, request_options=request_options, @@ -111,7 +111,7 @@ async def create_token( } return await self._client.request( method="post", - path="widgets/token", + path=("widgets", "token"), body=body, model=WidgetSessionTokenResponse, request_options=request_options, diff --git a/tests/test_actions.py b/tests/test_actions.py index 1d65d8f3..eb107905 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -63,6 +63,17 @@ def test_verify_header_stale_timestamp(self): tolerance=30, ) + def test_verify_header_future_timestamp(self): + future_ts = int((time.time() + 60) * 1000) + sig = _make_sig_header(SAMPLE_ACTION_PAYLOAD, SECRET, future_ts) + with pytest.raises(ValueError, match="tolerance zone"): + self.actions.verify_header( + payload=SAMPLE_ACTION_PAYLOAD, + sig_header=sig, + secret=SECRET, + tolerance=30, + ) + def test_verify_header_custom_tolerance(self): old_ts = int((time.time() - 10) * 1000) sig = _make_sig_header(SAMPLE_ACTION_PAYLOAD, SECRET, old_ts) diff --git a/tests/test_generated_client.py b/tests/test_generated_client.py index a675cc43..3c059fcd 100644 --- a/tests/test_generated_client.py +++ b/tests/test_generated_client.py @@ -61,7 +61,7 @@ def test_raises_400(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(BadRequestError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_401(self, httpx_mock): @@ -73,7 +73,7 @@ def test_raises_401(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthenticationError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_403(self, httpx_mock): @@ -85,7 +85,7 @@ def test_raises_403(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_email_verification_required(self, httpx_mock): @@ -103,7 +103,7 @@ def test_raises_email_verification_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(EmailVerificationRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_123" assert exc_info.value.email_verification_id == "ev_123" assert exc_info.value.email == "user@example.com" @@ -123,7 +123,7 @@ def test_raises_mfa_enrollment(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(MfaEnrollmentError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_456" assert exc_info.value.user == {"id": "user_123", "email": "user@example.com"} client.close() @@ -143,7 +143,7 @@ def test_raises_mfa_challenge(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(MfaChallengeError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_789" assert exc_info.value.user == {"id": "user_123"} assert exc_info.value.authentication_factors == [{"id": "af_1", "type": "totp"}] @@ -164,7 +164,7 @@ def test_raises_organization_selection_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(OrganizationSelectionRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_org" assert exc_info.value.organizations == [{"id": "org_1", "name": "Acme"}] client.close() @@ -184,7 +184,7 @@ def test_raises_sso_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(SsoRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_sso" assert exc_info.value.email == "user@example.com" assert exc_info.value.connection_ids == ["conn_1", "conn_2"] @@ -206,7 +206,7 @@ def test_raises_org_auth_methods_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(OrganizationAuthMethodsRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_oam" assert exc_info.value.sso_connection_ids == ["conn_1"] assert exc_info.value.auth_methods == {"google_oauth": True, "password": False} @@ -235,7 +235,7 @@ def test_raises_simple_auth_flow_errors(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(expected_class) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_tok" client.close() @@ -252,7 +252,7 @@ def test_auth_flow_errors_are_catchable_as_authorization_error(self, httpx_mock) api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_auth_flow_errors_are_catchable_as_authentication_flow_error( @@ -270,7 +270,7 @@ def test_auth_flow_errors_are_catchable_as_authentication_flow_error( api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthenticationFlowError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_unknown_403_code_raises_authorization_error(self, httpx_mock): @@ -282,7 +282,7 @@ def test_unknown_403_code_raises_authorization_error(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert not isinstance(exc_info.value, AuthenticationFlowError) client.close() @@ -295,7 +295,7 @@ def test_raises_404(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(NotFoundError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_409(self, httpx_mock): @@ -307,7 +307,7 @@ def test_raises_409(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ConflictError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_422(self, httpx_mock): @@ -319,7 +319,7 @@ def test_raises_422(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(UnprocessableEntityError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_429(self, httpx_mock): @@ -331,7 +331,7 @@ def test_raises_429(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(RateLimitExceededError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_500(self, httpx_mock): @@ -343,13 +343,13 @@ def test_raises_500(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ServerError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_idempotency_key_on_post(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(api_key="sk_test_123", client_id="client_test") - client.request("POST", "test") + client.request("POST", ("test",)) request = httpx_mock.get_request() assert "Idempotency-Key" in request.headers client.close() @@ -357,7 +357,7 @@ def test_idempotency_key_on_post(self, httpx_mock): def test_no_idempotency_key_on_get(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(api_key="sk_test_123", client_id="client_test") - client.request("GET", "test") + client.request("GET", ("test",)) request = httpx_mock.get_request() assert "Idempotency-Key" not in request.headers client.close() @@ -365,7 +365,7 @@ def test_no_idempotency_key_on_get(self, httpx_mock): def test_no_authorization_header_without_api_key(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(client_id="client_test") - client.request("GET", "test") + client.request("GET", ("test",)) request = httpx_mock.get_request() assert "Authorization" not in request.headers client.close() @@ -373,7 +373,7 @@ def test_no_authorization_header_without_api_key(self, httpx_mock): def test_empty_body_sends_json(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(api_key="sk_test_123", client_id="client_test") - client.request("PUT", "test", body={}) + client.request("PUT", ("test",), body={}) request = httpx_mock.get_request() assert request.content == b"{}" client.close() @@ -393,7 +393,7 @@ def test_retry_exhaustion_raises_rate_limit(self, httpx_mock, monkeypatch): api_key="sk_test_123", client_id="client_test", max_retries=3 ) with pytest.raises(RateLimitExceededError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_rate_limit_retry_after_is_parsed(self, httpx_mock): @@ -406,7 +406,7 @@ def test_rate_limit_retry_after_is_parsed(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(RateLimitExceededError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.retry_after == 30.0 client.close() @@ -417,7 +417,7 @@ def test_timeout_error_is_wrapped(self, httpx_mock, monkeypatch): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSTimeoutError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_connection_error_is_wrapped(self, httpx_mock, monkeypatch): @@ -427,7 +427,7 @@ def test_connection_error_is_wrapped(self, httpx_mock, monkeypatch): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSConnectionError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_documented_import_surface_exposes_resources(self): @@ -456,7 +456,7 @@ def test_request_raw_preserves_json_dict_response(self, httpx_mock): client = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = client.request_raw("GET", "test") + result = client.request_raw("GET", ("test",)) assert result == {"ok": True} client.close() @@ -465,7 +465,7 @@ def test_request_list_preserves_json_array_response(self, httpx_mock): client = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = client.request_list("GET", "test") + result = client.request_list("GET", ("test",)) assert result == [{"id": "item_123"}] client.close() @@ -474,7 +474,7 @@ def test_request_returns_none_for_non_json_success_without_model(self, httpx_moc client = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = client.request("DELETE", "test") + result = client.request("DELETE", ("test",)) assert result is None client.close() @@ -507,7 +507,7 @@ async def test_request_raw_preserves_json_dict_response(self, httpx_mock): client = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = await client.request_raw("GET", "test") + result = await client.request_raw("GET", ("test",)) assert result == {"ok": True} await client.close() @@ -516,7 +516,7 @@ async def test_request_list_preserves_json_array_response(self, httpx_mock): client = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = await client.request_list("GET", "test") + result = await client.request_list("GET", ("test",)) assert result == [{"id": "item_123"}] await client.close() @@ -527,7 +527,7 @@ async def test_request_returns_none_for_non_json_success_without_model( client = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = await client.request("DELETE", "test") + result = await client.request("DELETE", ("test",)) assert result is None await client.close() @@ -540,7 +540,7 @@ async def test_raises_400(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(BadRequestError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_401(self, httpx_mock): @@ -552,7 +552,7 @@ async def test_raises_401(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthenticationError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_403(self, httpx_mock): @@ -564,7 +564,7 @@ async def test_raises_403(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_email_verification_required(self, httpx_mock): @@ -582,7 +582,7 @@ async def test_raises_email_verification_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(EmailVerificationRequiredError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_123" assert exc_info.value.email_verification_id == "ev_123" assert exc_info.value.email == "user@example.com" @@ -603,7 +603,7 @@ async def test_raises_mfa_challenge(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(MfaChallengeError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_789" assert exc_info.value.authentication_factors == [{"id": "af_1", "type": "totp"}] await client.close() @@ -623,7 +623,7 @@ async def test_raises_sso_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(SsoRequiredError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_sso" assert exc_info.value.connection_ids == ["conn_1"] await client.close() @@ -641,7 +641,7 @@ async def test_auth_flow_errors_backward_compat(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_unknown_403_code_raises_authorization_error(self, httpx_mock): @@ -653,7 +653,7 @@ async def test_unknown_403_code_raises_authorization_error(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert not isinstance(exc_info.value, AuthenticationFlowError) await client.close() @@ -666,7 +666,7 @@ async def test_raises_404(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(NotFoundError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_409(self, httpx_mock): @@ -678,7 +678,7 @@ async def test_raises_409(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ConflictError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_422(self, httpx_mock): @@ -690,7 +690,7 @@ async def test_raises_422(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(UnprocessableEntityError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_429(self, httpx_mock): @@ -702,7 +702,7 @@ async def test_raises_429(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(RateLimitExceededError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_500(self, httpx_mock): @@ -714,7 +714,7 @@ async def test_raises_500(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ServerError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_timeout_error_is_wrapped(self, httpx_mock, monkeypatch): @@ -727,7 +727,7 @@ async def _sleep(_: float) -> None: api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSTimeoutError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_connection_error_is_wrapped(self, httpx_mock, monkeypatch): @@ -740,5 +740,5 @@ async def _sleep(_: float) -> None: api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSConnectionError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() diff --git a/tests/test_session.py b/tests/test_session.py index 3355bfb2..15e44e78 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -7,6 +7,19 @@ from cryptography.hazmat.primitives.asymmetric import rsa from workos import WorkOSClient +from workos._errors import ( + AuthenticationError, + AuthenticationMethodNotAllowedError, + EmailVerificationRequiredError, + MfaChallengeError, + MfaEnrollmentError, + OrganizationAuthMethodsRequiredError, + OrganizationSelectionRequiredError, + RadarChallengeError, + SsoRequiredError, + WorkOSConnectionError, + WorkOSTimeoutError, +) from workos.session import ( AsyncSession, AuthenticateWithSessionCookieErrorResponse, @@ -14,6 +27,7 @@ AuthenticateWithSessionCookieSuccessResponse, RefreshWithSessionCookieErrorResponse, Session, + _map_refresh_exception_to_reason, seal_data, seal_session_from_auth_response, unseal_data, @@ -212,6 +226,64 @@ def test_session_refresh_missing_refresh_token(self): assert isinstance(result, RefreshWithSessionCookieErrorResponse) +class TestMapRefreshExceptionToReason: + @pytest.mark.parametrize( + "exc, expected", + [ + ( + MfaChallengeError("mfa challenge"), + AuthenticateWithSessionCookieFailureReason.MFA_CHALLENGE_REQUIRED, + ), + ( + MfaEnrollmentError("mfa enrollment"), + AuthenticateWithSessionCookieFailureReason.MFA_ENROLLMENT_REQUIRED, + ), + ( + SsoRequiredError("sso required"), + AuthenticateWithSessionCookieFailureReason.SSO_REQUIRED, + ), + ( + EmailVerificationRequiredError("email verification required"), + AuthenticateWithSessionCookieFailureReason.EMAIL_VERIFICATION_REQUIRED, + ), + ( + OrganizationSelectionRequiredError("org selection required"), + AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED, + ), + ( + OrganizationAuthMethodsRequiredError("org auth methods required"), + AuthenticateWithSessionCookieFailureReason.ORGANIZATION_AUTH_METHODS_REQUIRED, + ), + ( + AuthenticationMethodNotAllowedError("method not allowed"), + AuthenticateWithSessionCookieFailureReason.AUTHENTICATION_METHOD_NOT_ALLOWED, + ), + ( + RadarChallengeError("radar challenge"), + AuthenticateWithSessionCookieFailureReason.RADAR_CHALLENGE_REQUIRED, + ), + ( + AuthenticationError("unauthorized"), + AuthenticateWithSessionCookieFailureReason.REFRESH_DENIED, + ), + ( + WorkOSConnectionError("connection failed"), + AuthenticateWithSessionCookieFailureReason.REFRESH_NETWORK_ERROR, + ), + ( + WorkOSTimeoutError("timeout"), + AuthenticateWithSessionCookieFailureReason.REFRESH_NETWORK_ERROR, + ), + ], + ) + def test_known_exceptions_map_to_reason(self, exc, expected): + assert _map_refresh_exception_to_reason(exc) == expected + + def test_unknown_exception_falls_back_to_string(self): + result = _map_refresh_exception_to_reason(RuntimeError("boom")) + assert result == "boom" + + @pytest.mark.asyncio class TestAsyncSession: def _mock_jwks(self, public_key): diff --git a/tests/test_webhook_verification.py b/tests/test_webhook_verification.py index aaa2ea05..ba98d5f1 100644 --- a/tests/test_webhook_verification.py +++ b/tests/test_webhook_verification.py @@ -124,6 +124,17 @@ def test_verify_header_stale_timestamp(self, workos): tolerance=180, ) + def test_verify_header_future_timestamp(self, workos): + future_ts = int((time.time() + 300) * 1000) + sig = _make_sig_header(SAMPLE_EVENT, SECRET, future_ts) + with pytest.raises(ValueError, match="tolerance zone"): + workos.webhooks.verify_header( + event_body=SAMPLE_EVENT, + event_signature=sig, + secret=SECRET, + tolerance=180, + ) + class TestStandaloneVerifyEvent: def test_standalone_verify_event(self): @@ -157,3 +168,25 @@ def test_standalone_verify_header_invalid(self): event_signature=sig, secret=SECRET, ) + + def test_standalone_verify_header_future_timestamp(self): + future_ts = int((time.time() + 300) * 1000) + sig = _make_sig_header(SAMPLE_EVENT, SECRET, future_ts) + with pytest.raises(ValueError, match="tolerance zone"): + standalone_verify_header( + event_body=SAMPLE_EVENT.encode("utf-8"), + event_signature=sig, + secret=SECRET, + tolerance=180, + ) + + def test_standalone_verify_header_tolerance_zero_rejects_old_timestamp(self): + old_ts = int((time.time() - 1) * 1000) + sig = _make_sig_header(SAMPLE_EVENT, SECRET, old_ts) + with pytest.raises(ValueError, match="tolerance zone"): + standalone_verify_header( + event_body=SAMPLE_EVENT.encode("utf-8"), + event_signature=sig, + secret=SECRET, + tolerance=0, + )