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
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
2 changes: 2 additions & 0 deletions stytch/consumer/models/connected_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
5 changes: 5 additions & 0 deletions stytch/consumer/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion stytch/shared/tests/test_policy_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion stytch/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "14.0.0"
__version__ = "14.1.0"