Skip to content

Commit b94a59b

Browse files
author
Stytch Codegen Bot
committed
WebAuthn - support URL encoding for authenticate_start
1 parent 7305671 commit b94a59b

File tree

13 files changed

+268
-2
lines changed

13 files changed

+268
-2
lines changed

stytch/b2b/api/organizations.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
CreateRequestFirstPartyConnectedAppsAllowedType,
1616
CreateRequestThirdPartyConnectedAppsAllowedType,
1717
CreateResponse,
18+
DeleteExternalIdRequestOptions,
19+
DeleteExternalIdResponse,
1820
DeleteRequestOptions,
1921
DeleteResponse,
2022
EmailImplicitRoleAssignment,
@@ -1171,3 +1173,39 @@ async def get_connected_app_async(
11711173
)
11721174
res = await self.async_client.get(url, data, headers)
11731175
return GetConnectedAppResponse.from_json(res.response.status, res.json)
1176+
1177+
def delete_external_id(
1178+
self,
1179+
organization_id: str,
1180+
method_options: Optional[DeleteExternalIdRequestOptions] = None,
1181+
) -> DeleteExternalIdResponse:
1182+
headers: Dict[str, str] = {}
1183+
if method_options is not None:
1184+
headers = method_options.add_headers(headers)
1185+
data: Dict[str, Any] = {
1186+
"organization_id": organization_id,
1187+
}
1188+
1189+
url = self.api_base.url_for(
1190+
"/v1/b2b/organizations/{organization_id}/external_id", data
1191+
)
1192+
res = self.sync_client.delete(url, headers)
1193+
return DeleteExternalIdResponse.from_json(res.response.status_code, res.json)
1194+
1195+
async def delete_external_id_async(
1196+
self,
1197+
organization_id: str,
1198+
method_options: Optional[DeleteExternalIdRequestOptions] = None,
1199+
) -> DeleteExternalIdResponse:
1200+
headers: Dict[str, str] = {}
1201+
if method_options is not None:
1202+
headers = method_options.add_headers(headers)
1203+
data: Dict[str, Any] = {
1204+
"organization_id": organization_id,
1205+
}
1206+
1207+
url = self.api_base.url_for(
1208+
"/v1/b2b/organizations/{organization_id}/external_id", data
1209+
)
1210+
res = await self.async_client.delete(url, headers)
1211+
return DeleteExternalIdResponse.from_json(res.response.status, res.json)

stytch/b2b/api/organizations_members.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from stytch.b2b.models.organizations_members import (
1515
CreateRequestOptions,
1616
CreateResponse,
17+
DeleteExternalIdRequestOptions,
18+
DeleteExternalIdResponse,
1719
DeleteMFAPhoneNumberRequestOptions,
1820
DeleteMFAPhoneNumberResponse,
1921
DeletePasswordRequestOptions,
@@ -1043,6 +1045,48 @@ async def get_connected_apps_async(
10431045
res = await self.async_client.get(url, data, headers)
10441046
return GetConnectedAppsResponse.from_json(res.response.status, res.json)
10451047

1048+
def delete_external_id(
1049+
self,
1050+
organization_id: str,
1051+
member_id: str,
1052+
method_options: Optional[DeleteExternalIdRequestOptions] = None,
1053+
) -> DeleteExternalIdResponse:
1054+
headers: Dict[str, str] = {}
1055+
if method_options is not None:
1056+
headers = method_options.add_headers(headers)
1057+
data: Dict[str, Any] = {
1058+
"organization_id": organization_id,
1059+
"member_id": member_id,
1060+
}
1061+
1062+
url = self.api_base.url_for(
1063+
"/v1/b2b/organizations/{organization_id}/members/{member_id}/external_id",
1064+
data,
1065+
)
1066+
res = self.sync_client.delete(url, headers)
1067+
return DeleteExternalIdResponse.from_json(res.response.status_code, res.json)
1068+
1069+
async def delete_external_id_async(
1070+
self,
1071+
organization_id: str,
1072+
member_id: str,
1073+
method_options: Optional[DeleteExternalIdRequestOptions] = None,
1074+
) -> DeleteExternalIdResponse:
1075+
headers: Dict[str, str] = {}
1076+
if method_options is not None:
1077+
headers = method_options.add_headers(headers)
1078+
data: Dict[str, Any] = {
1079+
"organization_id": organization_id,
1080+
"member_id": member_id,
1081+
}
1082+
1083+
url = self.api_base.url_for(
1084+
"/v1/b2b/organizations/{organization_id}/members/{member_id}/external_id",
1085+
data,
1086+
)
1087+
res = await self.async_client.delete(url, headers)
1088+
return DeleteExternalIdResponse.from_json(res.response.status, res.json)
1089+
10461090
def create(
10471091
self,
10481092
organization_id: str,

stytch/b2b/api/rbac_organizations.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,28 @@ def set_org_policy(
8686
organization_id: str,
8787
org_policy: Optional[Union[OrgPolicy, Dict[str, Any]]] = None,
8888
) -> SetOrgPolicyResponse:
89+
"""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.
90+
91+
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.
92+
93+
The organization policy consists of roles, where each role defines:
94+
- A unique `role_id` to identify the role
95+
- A human-readable `description` of the role's purpose
96+
- A set of `permissions` that specify which actions can be performed on which resources
97+
98+
When you set an organization policy, it will replace any existing organization-specific roles for that organization. The project-level RBAC policy remains unchanged.
99+
100+
Organization-specific roles are useful for scenarios where different organizations within your project require different permission structures, such as:
101+
- Multi-tenant applications with varying access levels per tenant
102+
- Organizations with custom approval workflows
103+
- Different organizational hierarchies requiring unique role definitions
104+
105+
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.
106+
107+
Fields:
108+
- 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.
109+
- 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.
110+
""" # noqa
89111
headers: Dict[str, str] = {}
90112
data: Dict[str, Any] = {
91113
"organization_id": organization_id,
@@ -106,6 +128,28 @@ async def set_org_policy_async(
106128
organization_id: str,
107129
org_policy: Optional[OrgPolicy] = None,
108130
) -> SetOrgPolicyResponse:
131+
"""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.
132+
133+
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.
134+
135+
The organization policy consists of roles, where each role defines:
136+
- A unique `role_id` to identify the role
137+
- A human-readable `description` of the role's purpose
138+
- A set of `permissions` that specify which actions can be performed on which resources
139+
140+
When you set an organization policy, it will replace any existing organization-specific roles for that organization. The project-level RBAC policy remains unchanged.
141+
142+
Organization-specific roles are useful for scenarios where different organizations within your project require different permission structures, such as:
143+
- Multi-tenant applications with varying access levels per tenant
144+
- Organizations with custom approval workflows
145+
- Different organizational hierarchies requiring unique role definitions
146+
147+
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.
148+
149+
Fields:
150+
- 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.
151+
- 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.
152+
""" # noqa
109153
headers: Dict[str, str] = {}
110154
data: Dict[str, Any] = {
111155
"organization_id": organization_id,

stytch/b2b/models/organizations.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,22 @@ class CustomRole(pydantic.BaseModel):
101101
permissions: List[CustomRolePermission]
102102

103103

104+
class DeleteExternalIdRequestOptions(pydantic.BaseModel):
105+
"""
106+
Fields:
107+
- authorization: Optional authorization object.
108+
Pass in an active Stytch Member session token or session JWT and the request
109+
will be run using that member's permissions.
110+
""" # noqa
111+
112+
authorization: Optional[Authorization] = None
113+
114+
def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
115+
if self.authorization is not None:
116+
headers = self.authorization.add_headers(headers)
117+
return headers
118+
119+
104120
class DeleteRequestOptions(pydantic.BaseModel):
105121
"""
106122
Fields:
@@ -668,6 +684,10 @@ class CreateResponse(ResponseBase):
668684
organization: Organization
669685

670686

687+
class DeleteExternalIdResponse(ResponseBase):
688+
organization: Organization
689+
690+
671691
class DeleteResponse(ResponseBase):
672692
"""Response type for `Organizations.delete`.
673693
Fields:

stytch/b2b/models/organizations_members.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
5050
return headers
5151

5252

53+
class DeleteExternalIdRequestOptions(pydantic.BaseModel):
54+
"""
55+
Fields:
56+
- authorization: Optional authorization object.
57+
Pass in an active Stytch Member session token or session JWT and the request
58+
will be run using that member's permissions.
59+
""" # noqa
60+
61+
authorization: Optional[Authorization] = None
62+
63+
def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
64+
if self.authorization is not None:
65+
headers = self.authorization.add_headers(headers)
66+
return headers
67+
68+
5369
class DeleteMFAPhoneNumberRequestOptions(pydantic.BaseModel):
5470
"""
5571
Fields:
@@ -223,6 +239,12 @@ class CreateResponse(ResponseBase):
223239
organization: Organization
224240

225241

242+
class DeleteExternalIdResponse(ResponseBase):
243+
member_id: str
244+
member: Member
245+
organization: Organization
246+
247+
226248
class DeleteMFAPhoneNumberResponse(ResponseBase):
227249
"""Response type for `Members.delete_mfa_phone_number`.
228250
Fields:

stytch/b2b/models/rbac_organizations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ class GetOrgPolicyResponse(ResponseBase):
2222

2323

2424
class SetOrgPolicyResponse(ResponseBase):
25+
"""Response type for `Organizations.set_org_policy`.
26+
Fields:
27+
- 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.
28+
""" # noqa
29+
2530
org_policy: Optional[OrgPolicy] = None

stytch/consumer/api/connected_apps_clients.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Any, Dict, List, Optional, Union
1010

1111
from stytch.consumer.api.connected_apps_clients_secrets import Secrets
12+
from stytch.consumer.models.connected_apps import SearchConnectedAppsQuery
1213
from stytch.consumer.models.connected_apps_clients import (
1314
CreateRequestClientType,
1415
CreateResponse,
@@ -230,19 +231,23 @@ def search(
230231
self,
231232
cursor: Optional[str] = None,
232233
limit: Optional[int] = None,
234+
query: Optional[Union[SearchConnectedAppsQuery, Dict[str, Any]]] = None,
233235
) -> SearchResponse:
234236
"""Search for Connected Apps. Supports cursor-based pagination. Specific filters coming soon.
235237
236238
Fields:
237239
- 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.
238240
- 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.
241+
- query: (no documentation yet)
239242
""" # noqa
240243
headers: Dict[str, str] = {}
241244
data: Dict[str, Any] = {}
242245
if cursor is not None:
243246
data["cursor"] = cursor
244247
if limit is not None:
245248
data["limit"] = limit
249+
if query is not None:
250+
data["query"] = query if isinstance(query, dict) else query.dict()
246251

247252
url = self.api_base.url_for("/v1/connected_apps/clients/search", data)
248253
res = self.sync_client.post(url, data, headers)
@@ -252,19 +257,23 @@ async def search_async(
252257
self,
253258
cursor: Optional[str] = None,
254259
limit: Optional[int] = None,
260+
query: Optional[SearchConnectedAppsQuery] = None,
255261
) -> SearchResponse:
256262
"""Search for Connected Apps. Supports cursor-based pagination. Specific filters coming soon.
257263
258264
Fields:
259265
- 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.
260266
- 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.
267+
- query: (no documentation yet)
261268
""" # noqa
262269
headers: Dict[str, str] = {}
263270
data: Dict[str, Any] = {}
264271
if cursor is not None:
265272
data["cursor"] = cursor
266273
if limit is not None:
267274
data["limit"] = limit
275+
if query is not None:
276+
data["query"] = query if isinstance(query, dict) else query.dict()
268277

269278
url = self.api_base.url_for("/v1/connected_apps/clients/search", data)
270279
res = await self.async_client.post(url, data, headers)

stytch/consumer/api/users.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
DeleteBiometricRegistrationResponse,
1616
DeleteCryptoWalletResponse,
1717
DeleteEmailResponse,
18+
DeleteExternalIdResponse,
1819
DeleteOAuthRegistrationResponse,
1920
DeletePasswordResponse,
2021
DeletePhoneNumberResponse,
@@ -771,6 +772,32 @@ async def delete_oauth_registration_async(
771772
res = await self.async_client.delete(url, headers)
772773
return DeleteOAuthRegistrationResponse.from_json(res.response.status, res.json)
773774

775+
def delete_external_id(
776+
self,
777+
user_id: str,
778+
) -> DeleteExternalIdResponse:
779+
headers: Dict[str, str] = {}
780+
data: Dict[str, Any] = {
781+
"user_id": user_id,
782+
}
783+
784+
url = self.api_base.url_for("/v1/users/{user_id}/external_id", data)
785+
res = self.sync_client.delete(url, headers)
786+
return DeleteExternalIdResponse.from_json(res.response.status_code, res.json)
787+
788+
async def delete_external_id_async(
789+
self,
790+
user_id: str,
791+
) -> DeleteExternalIdResponse:
792+
headers: Dict[str, str] = {}
793+
data: Dict[str, Any] = {
794+
"user_id": user_id,
795+
}
796+
797+
url = self.api_base.url_for("/v1/users/{user_id}/external_id", data)
798+
res = await self.async_client.delete(url, headers)
799+
return DeleteExternalIdResponse.from_json(res.response.status, res.json)
800+
774801
def connected_apps(
775802
self,
776803
user_id: str,

stytch/consumer/api/webauthn.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def authenticate_start(
255255
domain: str,
256256
user_id: Optional[str] = None,
257257
return_passkey_credential_options: Optional[bool] = None,
258+
use_base64_url_encoding: Optional[bool] = None,
258259
) -> AuthenticateStartResponse:
259260
"""Initiate the authentication of a Passkey or WebAuthn registration.
260261
@@ -269,6 +270,7 @@ def authenticate_start(
269270
- 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.
270271
- return_passkey_credential_options: If true, the `public_key_credential_creation_options` returned will be optimized for Passkeys with `userVerification` set to `"preferred"`.
271272
273+
- use_base64_url_encoding: (no documentation yet)
272274
""" # noqa
273275
headers: Dict[str, str] = {}
274276
data: Dict[str, Any] = {
@@ -280,6 +282,8 @@ def authenticate_start(
280282
data["return_passkey_credential_options"] = (
281283
return_passkey_credential_options
282284
)
285+
if use_base64_url_encoding is not None:
286+
data["use_base64_url_encoding"] = use_base64_url_encoding
283287

284288
url = self.api_base.url_for("/v1/webauthn/authenticate/start", data)
285289
res = self.sync_client.post(url, data, headers)
@@ -290,6 +294,7 @@ async def authenticate_start_async(
290294
domain: str,
291295
user_id: Optional[str] = None,
292296
return_passkey_credential_options: Optional[bool] = None,
297+
use_base64_url_encoding: Optional[bool] = None,
293298
) -> AuthenticateStartResponse:
294299
"""Initiate the authentication of a Passkey or WebAuthn registration.
295300
@@ -304,6 +309,7 @@ async def authenticate_start_async(
304309
- 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.
305310
- return_passkey_credential_options: If true, the `public_key_credential_creation_options` returned will be optimized for Passkeys with `userVerification` set to `"preferred"`.
306311
312+
- use_base64_url_encoding: (no documentation yet)
307313
""" # noqa
308314
headers: Dict[str, str] = {}
309315
data: Dict[str, Any] = {
@@ -315,6 +321,8 @@ async def authenticate_start_async(
315321
data["return_passkey_credential_options"] = (
316322
return_passkey_credential_options
317323
)
324+
if use_base64_url_encoding is not None:
325+
data["use_base64_url_encoding"] = use_base64_url_encoding
318326

319327
url = self.api_base.url_for("/v1/webauthn/authenticate/start", data)
320328
res = await self.async_client.post(url, data, headers)

0 commit comments

Comments
 (0)