From abd90104164a9831327de13dcfdfc6eebdb6bbdc Mon Sep 17 00:00:00 2001 From: Stytch Codegen Bot Date: Thu, 15 Jan 2026 17:45:45 +0000 Subject: [PATCH] Testing --- stytch/b2b/api/organizations.py | 38 +++++++++++++++++++ stytch/b2b/api/organizations_members.py | 44 ++++++++++++++++++++++ stytch/b2b/api/rbac_organizations.py | 44 ++++++++++++++++++++++ stytch/b2b/models/organizations.py | 20 ++++++++++ stytch/b2b/models/organizations_members.py | 22 +++++++++++ stytch/b2b/models/rbac_organizations.py | 5 +++ stytch/consumer/api/users.py | 27 +++++++++++++ stytch/consumer/api/webauthn.py | 8 ++++ stytch/consumer/models/connected_apps.py | 2 + stytch/consumer/models/users.py | 5 +++ stytch/shared/tests/test_policy_cache.py | 2 +- stytch/version.py | 2 +- 12 files changed, 217 insertions(+), 2 deletions(-) diff --git a/stytch/b2b/api/organizations.py b/stytch/b2b/api/organizations.py index fd683158..9fb452cc 100644 --- a/stytch/b2b/api/organizations.py +++ b/stytch/b2b/api/organizations.py @@ -15,6 +15,8 @@ CreateRequestFirstPartyConnectedAppsAllowedType, CreateRequestThirdPartyConnectedAppsAllowedType, CreateResponse, + DeleteExternalIdRequestOptions, + DeleteExternalIdResponse, DeleteRequestOptions, DeleteResponse, EmailImplicitRoleAssignment, @@ -1171,3 +1173,39 @@ async def get_connected_app_async( ) res = await self.async_client.get(url, data, headers) return GetConnectedAppResponse.from_json(res.response.status, res.json) + + def delete_external_id( + self, + organization_id: str, + method_options: Optional[DeleteExternalIdRequestOptions] = None, + ) -> DeleteExternalIdResponse: + headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) + data: Dict[str, Any] = { + "organization_id": organization_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/external_id", data + ) + res = self.sync_client.delete(url, headers) + return DeleteExternalIdResponse.from_json(res.response.status_code, res.json) + + async def delete_external_id_async( + self, + organization_id: str, + method_options: Optional[DeleteExternalIdRequestOptions] = None, + ) -> DeleteExternalIdResponse: + headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) + data: Dict[str, Any] = { + "organization_id": organization_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/external_id", data + ) + res = await self.async_client.delete(url, headers) + return DeleteExternalIdResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/organizations_members.py b/stytch/b2b/api/organizations_members.py index 779cb3a9..acf83cba 100644 --- a/stytch/b2b/api/organizations_members.py +++ b/stytch/b2b/api/organizations_members.py @@ -14,6 +14,8 @@ from stytch.b2b.models.organizations_members import ( CreateRequestOptions, CreateResponse, + DeleteExternalIdRequestOptions, + DeleteExternalIdResponse, DeleteMFAPhoneNumberRequestOptions, DeleteMFAPhoneNumberResponse, DeletePasswordRequestOptions, @@ -1043,6 +1045,48 @@ async def get_connected_apps_async( res = await self.async_client.get(url, data, headers) return GetConnectedAppsResponse.from_json(res.response.status, res.json) + def delete_external_id( + self, + organization_id: str, + member_id: str, + method_options: Optional[DeleteExternalIdRequestOptions] = None, + ) -> DeleteExternalIdResponse: + headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/external_id", + data, + ) + res = self.sync_client.delete(url, headers) + return DeleteExternalIdResponse.from_json(res.response.status_code, res.json) + + async def delete_external_id_async( + self, + organization_id: str, + member_id: str, + method_options: Optional[DeleteExternalIdRequestOptions] = None, + ) -> DeleteExternalIdResponse: + headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/external_id", + data, + ) + res = await self.async_client.delete(url, headers) + return DeleteExternalIdResponse.from_json(res.response.status, res.json) + def create( self, organization_id: str, diff --git a/stytch/b2b/api/rbac_organizations.py b/stytch/b2b/api/rbac_organizations.py index a6d9d983..520f95df 100644 --- a/stytch/b2b/api/rbac_organizations.py +++ b/stytch/b2b/api/rbac_organizations.py @@ -86,6 +86,28 @@ def set_org_policy( organization_id: str, org_policy: Optional[Union[OrgPolicy, Dict[str, Any]]] = None, ) -> SetOrgPolicyResponse: + """Set the RBAC Policy for a specific Organization within your Stytch Project. An Organization RBAC Policy allows you to define roles that are specific to that organization, providing fine-grained control over permissions at the organization level. + + This endpoint allows you to create, update, or replace the organization-scoped roles for a given organization. Organization policies supplement the project-level RBAC policy with additional roles that are only applicable within the context of that specific organization. + + The organization policy consists of roles, where each role defines: + - A unique `role_id` to identify the role + - A human-readable `description` of the role's purpose + - A set of `permissions` that specify which actions can be performed on which resources + + When you set an organization policy, it will replace any existing organization-specific roles for that organization. The project-level RBAC policy remains unchanged. + + Organization-specific roles are useful for scenarios where different organizations within your project require different permission structures, such as: + - Multi-tenant applications with varying access levels per tenant + - Organizations with custom approval workflows + - Different organizational hierarchies requiring unique role definitions + + Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model and organization-scoped policies. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug or organization_external_id here as a convenience. + - org_policy: The organization-specific RBAC Policy that contains roles defined for this organization. Organization policies supplement the project-level RBAC policy with additional roles that are specific to the organization. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { "organization_id": organization_id, @@ -106,6 +128,28 @@ async def set_org_policy_async( organization_id: str, org_policy: Optional[OrgPolicy] = None, ) -> SetOrgPolicyResponse: + """Set the RBAC Policy for a specific Organization within your Stytch Project. An Organization RBAC Policy allows you to define roles that are specific to that organization, providing fine-grained control over permissions at the organization level. + + This endpoint allows you to create, update, or replace the organization-scoped roles for a given organization. Organization policies supplement the project-level RBAC policy with additional roles that are only applicable within the context of that specific organization. + + The organization policy consists of roles, where each role defines: + - A unique `role_id` to identify the role + - A human-readable `description` of the role's purpose + - A set of `permissions` that specify which actions can be performed on which resources + + When you set an organization policy, it will replace any existing organization-specific roles for that organization. The project-level RBAC policy remains unchanged. + + Organization-specific roles are useful for scenarios where different organizations within your project require different permission structures, such as: + - Multi-tenant applications with varying access levels per tenant + - Organizations with custom approval workflows + - Different organizational hierarchies requiring unique role definitions + + Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model and organization-scoped policies. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug or organization_external_id here as a convenience. + - org_policy: The organization-specific RBAC Policy that contains roles defined for this organization. Organization policies supplement the project-level RBAC policy with additional roles that are specific to the organization. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { "organization_id": organization_id, diff --git a/stytch/b2b/models/organizations.py b/stytch/b2b/models/organizations.py index 2a29d8e3..c54cd5b3 100644 --- a/stytch/b2b/models/organizations.py +++ b/stytch/b2b/models/organizations.py @@ -101,6 +101,22 @@ class CustomRole(pydantic.BaseModel): permissions: List[CustomRolePermission] +class DeleteExternalIdRequestOptions(pydantic.BaseModel): + """ + Fields: + - authorization: Optional authorization object. + Pass in an active Stytch Member session token or session JWT and the request + will be run using that member's permissions. + """ # noqa + + authorization: Optional[Authorization] = None + + def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: + if self.authorization is not None: + headers = self.authorization.add_headers(headers) + return headers + + class DeleteRequestOptions(pydantic.BaseModel): """ Fields: @@ -668,6 +684,10 @@ class CreateResponse(ResponseBase): organization: Organization +class DeleteExternalIdResponse(ResponseBase): + organization: Organization + + class DeleteResponse(ResponseBase): """Response type for `Organizations.delete`. Fields: diff --git a/stytch/b2b/models/organizations_members.py b/stytch/b2b/models/organizations_members.py index 08ee9d1f..8f325d34 100644 --- a/stytch/b2b/models/organizations_members.py +++ b/stytch/b2b/models/organizations_members.py @@ -50,6 +50,22 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: return headers +class DeleteExternalIdRequestOptions(pydantic.BaseModel): + """ + Fields: + - authorization: Optional authorization object. + Pass in an active Stytch Member session token or session JWT and the request + will be run using that member's permissions. + """ # noqa + + authorization: Optional[Authorization] = None + + def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: + if self.authorization is not None: + headers = self.authorization.add_headers(headers) + return headers + + class DeleteMFAPhoneNumberRequestOptions(pydantic.BaseModel): """ Fields: @@ -223,6 +239,12 @@ class CreateResponse(ResponseBase): organization: Organization +class DeleteExternalIdResponse(ResponseBase): + member_id: str + member: Member + organization: Organization + + class DeleteMFAPhoneNumberResponse(ResponseBase): """Response type for `Members.delete_mfa_phone_number`. Fields: diff --git a/stytch/b2b/models/rbac_organizations.py b/stytch/b2b/models/rbac_organizations.py index 7e62489a..41b3061a 100644 --- a/stytch/b2b/models/rbac_organizations.py +++ b/stytch/b2b/models/rbac_organizations.py @@ -22,4 +22,9 @@ class GetOrgPolicyResponse(ResponseBase): class SetOrgPolicyResponse(ResponseBase): + """Response type for `Organizations.set_org_policy`. + Fields: + - org_policy: The organization-specific RBAC Policy that contains roles defined for this organization. Organization policies supplement the project-level RBAC policy with additional roles that are specific to the organization. + """ # noqa + org_policy: Optional[OrgPolicy] = None diff --git a/stytch/consumer/api/users.py b/stytch/consumer/api/users.py index 549ea533..1030d2e4 100644 --- a/stytch/consumer/api/users.py +++ b/stytch/consumer/api/users.py @@ -15,6 +15,7 @@ DeleteBiometricRegistrationResponse, DeleteCryptoWalletResponse, DeleteEmailResponse, + DeleteExternalIdResponse, DeleteOAuthRegistrationResponse, DeletePasswordResponse, DeletePhoneNumberResponse, @@ -771,6 +772,32 @@ async def delete_oauth_registration_async( res = await self.async_client.delete(url, headers) return DeleteOAuthRegistrationResponse.from_json(res.response.status, res.json) + def delete_external_id( + self, + user_id: str, + ) -> DeleteExternalIdResponse: + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "user_id": user_id, + } + + url = self.api_base.url_for("/v1/users/{user_id}/external_id", data) + res = self.sync_client.delete(url, headers) + return DeleteExternalIdResponse.from_json(res.response.status_code, res.json) + + async def delete_external_id_async( + self, + user_id: str, + ) -> DeleteExternalIdResponse: + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "user_id": user_id, + } + + url = self.api_base.url_for("/v1/users/{user_id}/external_id", data) + res = await self.async_client.delete(url, headers) + return DeleteExternalIdResponse.from_json(res.response.status, res.json) + def connected_apps( self, user_id: str, diff --git a/stytch/consumer/api/webauthn.py b/stytch/consumer/api/webauthn.py index cdf825bd..8899fbb1 100644 --- a/stytch/consumer/api/webauthn.py +++ b/stytch/consumer/api/webauthn.py @@ -255,6 +255,7 @@ def authenticate_start( domain: str, user_id: Optional[str] = None, return_passkey_credential_options: Optional[bool] = None, + use_base64_url_encoding: Optional[bool] = None, ) -> AuthenticateStartResponse: """Initiate the authentication of a Passkey or WebAuthn registration. @@ -269,6 +270,7 @@ def authenticate_start( - user_id: The `user_id` of an active user the Passkey or WebAuthn registration should be tied to. You may use an `external_id` here if one is set for the user. - return_passkey_credential_options: If true, the `public_key_credential_creation_options` returned will be optimized for Passkeys with `userVerification` set to `"preferred"`. + - use_base64_url_encoding: (no documentation yet) """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -280,6 +282,8 @@ def authenticate_start( data["return_passkey_credential_options"] = ( return_passkey_credential_options ) + if use_base64_url_encoding is not None: + data["use_base64_url_encoding"] = use_base64_url_encoding url = self.api_base.url_for("/v1/webauthn/authenticate/start", data) res = self.sync_client.post(url, data, headers) @@ -290,6 +294,7 @@ async def authenticate_start_async( domain: str, user_id: Optional[str] = None, return_passkey_credential_options: Optional[bool] = None, + use_base64_url_encoding: Optional[bool] = None, ) -> AuthenticateStartResponse: """Initiate the authentication of a Passkey or WebAuthn registration. @@ -304,6 +309,7 @@ async def authenticate_start_async( - user_id: The `user_id` of an active user the Passkey or WebAuthn registration should be tied to. You may use an `external_id` here if one is set for the user. - return_passkey_credential_options: If true, the `public_key_credential_creation_options` returned will be optimized for Passkeys with `userVerification` set to `"preferred"`. + - use_base64_url_encoding: (no documentation yet) """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -315,6 +321,8 @@ async def authenticate_start_async( data["return_passkey_credential_options"] = ( return_passkey_credential_options ) + if use_base64_url_encoding is not None: + data["use_base64_url_encoding"] = use_base64_url_encoding url = self.api_base.url_for("/v1/webauthn/authenticate/start", data) res = await self.async_client.post(url, data, headers) diff --git a/stytch/consumer/models/connected_apps.py b/stytch/consumer/models/connected_apps.py index c3a095a9..bf44910b 100644 --- a/stytch/consumer/models/connected_apps.py +++ b/stytch/consumer/models/connected_apps.py @@ -30,6 +30,7 @@ class ConnectedApp(pydantic.BaseModel): - access_token_custom_audience: (no documentation yet) - logo_url: The logo URL of the Connected App, if any. - client_id_metadata_url: (no documentation yet) + - creation_method: (no documentation yet) """ # noqa client_id: str @@ -48,6 +49,7 @@ class ConnectedApp(pydantic.BaseModel): access_token_custom_audience: Optional[str] = None logo_url: Optional[str] = None client_id_metadata_url: Optional[str] = None + creation_method: Optional[str] = None class ConnectedAppPublic(pydantic.BaseModel): diff --git a/stytch/consumer/models/users.py b/stytch/consumer/models/users.py index 2557ac6c..121ea0e6 100644 --- a/stytch/consumer/models/users.py +++ b/stytch/consumer/models/users.py @@ -293,6 +293,11 @@ class DeleteEmailResponse(ResponseBase): user: User +class DeleteExternalIdResponse(ResponseBase): + user_id: str + user: User + + class DeleteOAuthRegistrationResponse(ResponseBase): """Response type for `Users.delete_oauth_registration`. Fields: diff --git a/stytch/shared/tests/test_policy_cache.py b/stytch/shared/tests/test_policy_cache.py index f8304058..064873f6 100644 --- a/stytch/shared/tests/test_policy_cache.py +++ b/stytch/shared/tests/test_policy_cache.py @@ -6,10 +6,10 @@ OrgPolicy, Policy, PolicyResource, + PolicyResponse, PolicyRole, PolicyRolePermission, PolicyScope, - PolicyResponse, ) from stytch.b2b.models.rbac_organizations import GetOrgPolicyResponse from stytch.shared.policy_cache import PolicyCache, _merge_policies diff --git a/stytch/version.py b/stytch/version.py index 22946915..217fb25c 100644 --- a/stytch/version.py +++ b/stytch/version.py @@ -1 +1 @@ -__version__ = "14.0.0" +__version__ = "14.1.0"