Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions stytch/b2b/api/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
CreateRequestFirstPartyConnectedAppsAllowedType,
CreateRequestThirdPartyConnectedAppsAllowedType,
CreateResponse,
DeleteExternalIdRequestOptions,
DeleteExternalIdResponse,
DeleteRequestOptions,
DeleteResponse,
EmailImplicitRoleAssignment,
Expand Down Expand Up @@ -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)
44 changes: 44 additions & 0 deletions stytch/b2b/api/organizations_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from stytch.b2b.models.organizations_members import (
CreateRequestOptions,
CreateResponse,
DeleteExternalIdRequestOptions,
DeleteExternalIdResponse,
DeleteMFAPhoneNumberRequestOptions,
DeleteMFAPhoneNumberResponse,
DeletePasswordRequestOptions,
Expand Down Expand Up @@ -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,
Expand Down
44 changes: 44 additions & 0 deletions stytch/b2b/api/rbac_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions stytch/b2b/models/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -668,6 +684,10 @@ class CreateResponse(ResponseBase):
organization: Organization


class DeleteExternalIdResponse(ResponseBase):
organization: Organization


class DeleteResponse(ResponseBase):
"""Response type for `Organizations.delete`.
Fields:
Expand Down
22 changes: 22 additions & 0 deletions stytch/b2b/models/organizations_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions stytch/b2b/models/rbac_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions stytch/consumer/api/connected_apps_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any, Dict, List, Optional, Union

from stytch.consumer.api.connected_apps_clients_secrets import Secrets
from stytch.consumer.models.connected_apps import SearchConnectedAppsQuery
from stytch.consumer.models.connected_apps_clients import (
CreateRequestClientType,
CreateResponse,
Expand Down Expand Up @@ -230,19 +231,23 @@ def search(
self,
cursor: Optional[str] = None,
limit: Optional[int] = None,
query: Optional[Union[SearchConnectedAppsQuery, Dict[str, Any]]] = None,
) -> SearchResponse:
"""Search for Connected Apps. Supports cursor-based pagination. Specific filters coming soon.

Fields:
- cursor: The `cursor` field allows you to paginate through your results. Each result array is limited to 1000 results. If your query returns more than 1000 results, you will need to paginate the responses using the `cursor`. If you receive a response that includes a non-null `next_cursor` in the `results_metadata` object, repeat the search call with the `next_cursor` value set to the `cursor` field to retrieve the next page of results. Continue to make search calls until the `next_cursor` in the response is null.
- limit: The number of search results to return per page. The default limit is 100. A maximum of 1000 results can be returned by a single search request. If the total size of your result set is greater than one page size, you must paginate the response. See the `cursor` field.
- query: (no documentation yet)
""" # noqa
headers: Dict[str, str] = {}
data: Dict[str, Any] = {}
if cursor is not None:
data["cursor"] = cursor
if limit is not None:
data["limit"] = limit
if query is not None:
data["query"] = query if isinstance(query, dict) else query.dict()

url = self.api_base.url_for("/v1/connected_apps/clients/search", data)
res = self.sync_client.post(url, data, headers)
Expand All @@ -252,19 +257,23 @@ async def search_async(
self,
cursor: Optional[str] = None,
limit: Optional[int] = None,
query: Optional[SearchConnectedAppsQuery] = None,
) -> SearchResponse:
"""Search for Connected Apps. Supports cursor-based pagination. Specific filters coming soon.

Fields:
- cursor: The `cursor` field allows you to paginate through your results. Each result array is limited to 1000 results. If your query returns more than 1000 results, you will need to paginate the responses using the `cursor`. If you receive a response that includes a non-null `next_cursor` in the `results_metadata` object, repeat the search call with the `next_cursor` value set to the `cursor` field to retrieve the next page of results. Continue to make search calls until the `next_cursor` in the response is null.
- limit: The number of search results to return per page. The default limit is 100. A maximum of 1000 results can be returned by a single search request. If the total size of your result set is greater than one page size, you must paginate the response. See the `cursor` field.
- query: (no documentation yet)
""" # noqa
headers: Dict[str, str] = {}
data: Dict[str, Any] = {}
if cursor is not None:
data["cursor"] = cursor
if limit is not None:
data["limit"] = limit
if query is not None:
data["query"] = query if isinstance(query, dict) else query.dict()

url = self.api_base.url_for("/v1/connected_apps/clients/search", data)
res = await self.async_client.post(url, data, headers)
Expand Down
27 changes: 27 additions & 0 deletions stytch/consumer/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
DeleteBiometricRegistrationResponse,
DeleteCryptoWalletResponse,
DeleteEmailResponse,
DeleteExternalIdResponse,
DeleteOAuthRegistrationResponse,
DeletePasswordResponse,
DeletePhoneNumberResponse,
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions stytch/consumer/api/webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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] = {
Expand All @@ -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)
Expand All @@ -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.

Expand All @@ -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] = {
Expand All @@ -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)
Expand Down
Loading