diff --git a/stytch/b2b/api/discovery_intermediate_sessions.py b/stytch/b2b/api/discovery_intermediate_sessions.py index b0522f1b..0ab394f1 100644 --- a/stytch/b2b/api/discovery_intermediate_sessions.py +++ b/stytch/b2b/api/discovery_intermediate_sessions.py @@ -8,7 +8,10 @@ from typing import Any, Dict, Optional -from stytch.b2b.models.discovery_intermediate_sessions import ExchangeResponse +from stytch.b2b.models.discovery_intermediate_sessions import ( + ExchangeRequestLocale, + ExchangeResponse, +) from stytch.core.api_base import ApiBase from stytch.core.http.client import AsyncClient, SyncClient @@ -30,6 +33,7 @@ def exchange( organization_id: str, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ExchangeRequestLocale] = None, ) -> ExchangeResponse: """Exchange an Intermediate Session for a fully realized [Member Session](https://stytch.com/docs/b2b/api/session-object) in a desired [Organization](https://stytch.com/docs/b2b/api/organization-object). This operation consumes the Intermediate Session. @@ -53,6 +57,7 @@ def exchange( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "intermediate_session_token": intermediate_session_token, @@ -62,6 +67,8 @@ def exchange( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for( "/v1/b2b/discovery/intermediate_sessions/exchange", data @@ -75,6 +82,7 @@ async def exchange_async( organization_id: str, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ExchangeRequestLocale] = None, ) -> ExchangeResponse: """Exchange an Intermediate Session for a fully realized [Member Session](https://stytch.com/docs/b2b/api/session-object) in a desired [Organization](https://stytch.com/docs/b2b/api/organization-object). This operation consumes the Intermediate Session. @@ -98,6 +106,7 @@ async def exchange_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "intermediate_session_token": intermediate_session_token, @@ -107,6 +116,8 @@ async def exchange_async( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for( "/v1/b2b/discovery/intermediate_sessions/exchange", data diff --git a/stytch/b2b/api/discovery_organizations.py b/stytch/b2b/api/discovery_organizations.py index d49e81a7..a4e68e11 100644 --- a/stytch/b2b/api/discovery_organizations.py +++ b/stytch/b2b/api/discovery_organizations.py @@ -39,6 +39,7 @@ def create( email_invites: Optional[str] = None, auth_methods: Optional[str] = None, allowed_auth_methods: Optional[List[str]] = None, + mfa_policy: Optional[str] = None, ) -> CreateResponse: """If an end user does not want to join any already-existing organization, or has no possible organizations to join, this endpoint can be used to create a new [Organization](https://stytch.com/docs/b2b/api/organization-object) and [Member](https://stytch.com/docs/b2b/api/member-object). @@ -103,6 +104,7 @@ def create( An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) """ # noqa data: Dict[str, Any] = { "intermediate_session_token": intermediate_session_token, @@ -129,6 +131,8 @@ def create( data["auth_methods"] = auth_methods if allowed_auth_methods is not None: data["allowed_auth_methods"] = allowed_auth_methods + if mfa_policy is not None: + data["mfa_policy"] = mfa_policy url = self.api_base.url_for("/v1/b2b/discovery/organizations/create", data) res = self.sync_client.post(url, data) @@ -149,6 +153,7 @@ async def create_async( email_invites: Optional[str] = None, auth_methods: Optional[str] = None, allowed_auth_methods: Optional[List[str]] = None, + mfa_policy: Optional[str] = None, ) -> CreateResponse: """If an end user does not want to join any already-existing organization, or has no possible organizations to join, this endpoint can be used to create a new [Organization](https://stytch.com/docs/b2b/api/organization-object) and [Member](https://stytch.com/docs/b2b/api/member-object). @@ -213,6 +218,7 @@ async def create_async( An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) """ # noqa data: Dict[str, Any] = { "intermediate_session_token": intermediate_session_token, @@ -239,6 +245,8 @@ async def create_async( data["auth_methods"] = auth_methods if allowed_auth_methods is not None: data["allowed_auth_methods"] = allowed_auth_methods + if mfa_policy is not None: + data["mfa_policy"] = mfa_policy url = self.api_base.url_for("/v1/b2b/discovery/organizations/create", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/api/magic_links.py b/stytch/b2b/api/magic_links.py index 72644c48..4070c3a7 100644 --- a/stytch/b2b/api/magic_links.py +++ b/stytch/b2b/api/magic_links.py @@ -10,7 +10,10 @@ from stytch.b2b.api.magic_links_discovery import Discovery from stytch.b2b.api.magic_links_email import Email -from stytch.b2b.models.magic_links import AuthenticateResponse +from stytch.b2b.models.magic_links import ( + AuthenticateRequestLocale, + AuthenticateResponse, +) from stytch.core.api_base import ApiBase from stytch.core.http.client import AsyncClient, SyncClient @@ -36,6 +39,7 @@ def authenticate( session_jwt: Optional[str] = None, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, ) -> AuthenticateResponse: """Authenticate a Member with a Magic Link. This endpoint requires a Magic Link token that is not expired or previously used. If the Member’s status is `pending` or `invited`, they will be updated to `active`. Provide the `session_duration_minutes` parameter to set the lifetime of the session. If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. @@ -62,6 +66,7 @@ def authenticate( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "magic_links_token": magic_links_token, @@ -76,6 +81,8 @@ def authenticate( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/magic_links/authenticate", data) res = self.sync_client.post(url, data) @@ -89,6 +96,7 @@ async def authenticate_async( session_jwt: Optional[str] = None, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, ) -> AuthenticateResponse: """Authenticate a Member with a Magic Link. This endpoint requires a Magic Link token that is not expired or previously used. If the Member’s status is `pending` or `invited`, they will be updated to `active`. Provide the `session_duration_minutes` parameter to set the lifetime of the session. If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. @@ -115,6 +123,7 @@ async def authenticate_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "magic_links_token": magic_links_token, @@ -129,6 +138,8 @@ async def authenticate_async( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/magic_links/authenticate", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/api/oauth.py b/stytch/b2b/api/oauth.py new file mode 100644 index 00000000..b30432c3 --- /dev/null +++ b/stytch/b2b/api/oauth.py @@ -0,0 +1,133 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import Any, Dict, Optional + +from stytch.b2b.api.oauth_discovery import Discovery +from stytch.b2b.models.oauth import AuthenticateRequestLocale, AuthenticateResponse +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class OAuth: + def __init__( + self, + api_base: ApiBase, + sync_client: SyncClient, + async_client: AsyncClient, + ) -> None: + self.api_base = api_base + self.sync_client = sync_client + self.async_client = async_client + self.discovery = Discovery(api_base, sync_client, async_client) + + def authenticate( + self, + oauth_token: str, + session_token: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_jwt: Optional[str] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + pkce_code_verifier: Optional[str] = None, + locale: Optional[AuthenticateRequestLocale] = None, + ) -> AuthenticateResponse: + """Authenticate a Member given a `token`. This endpoint verifies that the member completed the OAuth flow by verifying that the token is valid and hasn't expired. Provide the `session_duration_minutes` parameter to set the lifetime of the session. If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. + + Fields: + - oauth_token: The token to authenticate. + - session_token: A secret token for a given Stytch Session. + - session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist, + returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of + five minutes regardless of the underlying session duration, and will need to be refreshed over time. + + This value must be a minimum of 5 and a maximum of 527040 minutes (366 days). + + If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes. + + If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. If you don't want + to use the Stytch session product, you can ignore the session fields in the response. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - session_custom_claims: Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in + `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To + delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. + Total custom claims size cannot exceed four kilobytes. + - pkce_code_verifier: A base64url encoded one time secret used to validate that the request starts and ends on the same device. + - locale: (no documentation yet) + """ # noqa + data: Dict[str, Any] = { + "oauth_token": oauth_token, + } + if session_token is not None: + data["session_token"] = session_token + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if pkce_code_verifier is not None: + data["pkce_code_verifier"] = pkce_code_verifier + if locale is not None: + data["locale"] = locale.value + + url = self.api_base.url_for("/v1/b2b/oauth/authenticate", data) + res = self.sync_client.post(url, data) + return AuthenticateResponse.from_json(res.response.status_code, res.json) + + async def authenticate_async( + self, + oauth_token: str, + session_token: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_jwt: Optional[str] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + pkce_code_verifier: Optional[str] = None, + locale: Optional[AuthenticateRequestLocale] = None, + ) -> AuthenticateResponse: + """Authenticate a Member given a `token`. This endpoint verifies that the member completed the OAuth flow by verifying that the token is valid and hasn't expired. Provide the `session_duration_minutes` parameter to set the lifetime of the session. If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. + + Fields: + - oauth_token: The token to authenticate. + - session_token: A secret token for a given Stytch Session. + - session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist, + returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of + five minutes regardless of the underlying session duration, and will need to be refreshed over time. + + This value must be a minimum of 5 and a maximum of 527040 minutes (366 days). + + If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes. + + If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. If you don't want + to use the Stytch session product, you can ignore the session fields in the response. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - session_custom_claims: Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in + `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To + delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. + Total custom claims size cannot exceed four kilobytes. + - pkce_code_verifier: A base64url encoded one time secret used to validate that the request starts and ends on the same device. + - locale: (no documentation yet) + """ # noqa + data: Dict[str, Any] = { + "oauth_token": oauth_token, + } + if session_token is not None: + data["session_token"] = session_token + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if pkce_code_verifier is not None: + data["pkce_code_verifier"] = pkce_code_verifier + if locale is not None: + data["locale"] = locale.value + + url = self.api_base.url_for("/v1/b2b/oauth/authenticate", data) + res = await self.async_client.post(url, data) + return AuthenticateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/oauth_discovery.py b/stytch/b2b/api/oauth_discovery.py new file mode 100644 index 00000000..5ed7f220 --- /dev/null +++ b/stytch/b2b/api/oauth_discovery.py @@ -0,0 +1,99 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import Any, Dict, Optional + +from stytch.b2b.models.oauth_discovery import AuthenticateResponse +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class Discovery: + def __init__( + self, + api_base: ApiBase, + sync_client: SyncClient, + async_client: AsyncClient, + ) -> None: + self.api_base = api_base + self.sync_client = sync_client + self.async_client = async_client + + def authenticate( + self, + discovery_oauth_token: str, + session_token: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_jwt: Optional[str] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + pkce_code_verifier: Optional[str] = None, + ) -> AuthenticateResponse: + """Authenticates the Discovery OAuth token and exchanges it for an Intermediate Session Token. Intermediate Session Tokens can be used for various Discovery login flows and are valid for 10 minutes. + + Fields: + - discovery_oauth_token: The Discovery OAuth token to authenticate. + - session_token: (no documentation yet) + - session_duration_minutes: (no documentation yet) + - session_jwt: (no documentation yet) + - session_custom_claims: (no documentation yet) + - pkce_code_verifier: A base64url encoded one time secret used to validate that the request starts and ends on the same device. + """ # noqa + data: Dict[str, Any] = { + "discovery_oauth_token": discovery_oauth_token, + } + if session_token is not None: + data["session_token"] = session_token + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if pkce_code_verifier is not None: + data["pkce_code_verifier"] = pkce_code_verifier + + url = self.api_base.url_for("/v1/b2b/oauth/discovery/authenticate", data) + res = self.sync_client.post(url, data) + return AuthenticateResponse.from_json(res.response.status_code, res.json) + + async def authenticate_async( + self, + discovery_oauth_token: str, + session_token: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_jwt: Optional[str] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + pkce_code_verifier: Optional[str] = None, + ) -> AuthenticateResponse: + """Authenticates the Discovery OAuth token and exchanges it for an Intermediate Session Token. Intermediate Session Tokens can be used for various Discovery login flows and are valid for 10 minutes. + + Fields: + - discovery_oauth_token: The Discovery OAuth token to authenticate. + - session_token: (no documentation yet) + - session_duration_minutes: (no documentation yet) + - session_jwt: (no documentation yet) + - session_custom_claims: (no documentation yet) + - pkce_code_verifier: A base64url encoded one time secret used to validate that the request starts and ends on the same device. + """ # noqa + data: Dict[str, Any] = { + "discovery_oauth_token": discovery_oauth_token, + } + if session_token is not None: + data["session_token"] = session_token + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if pkce_code_verifier is not None: + data["pkce_code_verifier"] = pkce_code_verifier + + url = self.api_base.url_for("/v1/b2b/oauth/discovery/authenticate", data) + res = await self.async_client.post(url, data) + return AuthenticateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/organizations.py b/stytch/b2b/api/organizations.py index 8c5ad4ff..212d5be6 100644 --- a/stytch/b2b/api/organizations.py +++ b/stytch/b2b/api/organizations.py @@ -45,6 +45,7 @@ def create( email_invites: Optional[str] = None, auth_methods: Optional[str] = None, allowed_auth_methods: Optional[List[str]] = None, + mfa_policy: Optional[str] = None, ) -> CreateResponse: """Creates an Organization. An `organization_name` and a unique `organization_slug` are required. @@ -93,6 +94,7 @@ def create( An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_name": organization_name, @@ -115,6 +117,8 @@ def create( data["auth_methods"] = auth_methods if allowed_auth_methods is not None: data["allowed_auth_methods"] = allowed_auth_methods + if mfa_policy is not None: + data["mfa_policy"] = mfa_policy url = self.api_base.url_for("/v1/b2b/organizations", data) res = self.sync_client.post(url, data) @@ -132,6 +136,7 @@ async def create_async( email_invites: Optional[str] = None, auth_methods: Optional[str] = None, allowed_auth_methods: Optional[List[str]] = None, + mfa_policy: Optional[str] = None, ) -> CreateResponse: """Creates an Organization. An `organization_name` and a unique `organization_slug` are required. @@ -180,6 +185,7 @@ async def create_async( An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_name": organization_name, @@ -202,6 +208,8 @@ async def create_async( data["auth_methods"] = auth_methods if allowed_auth_methods is not None: data["allowed_auth_methods"] = allowed_auth_methods + if mfa_policy is not None: + data["mfa_policy"] = mfa_policy url = self.api_base.url_for("/v1/b2b/organizations", data) res = await self.async_client.post(url, data) @@ -256,6 +264,7 @@ def update( email_invites: Optional[str] = None, auth_methods: Optional[str] = None, allowed_auth_methods: Optional[List[str]] = None, + mfa_policy: Optional[str] = None, ) -> UpdateResponse: """Updates an Organization specified by `organization_id`. An Organization must always have at least one auth setting set to either `RESTRICTED` or `ALL_ALLOWED` in order to provision new Members. test @@ -306,6 +315,7 @@ def update( An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -336,6 +346,8 @@ def update( data["auth_methods"] = auth_methods if allowed_auth_methods is not None: data["allowed_auth_methods"] = allowed_auth_methods + if mfa_policy is not None: + data["mfa_policy"] = mfa_policy url = self.api_base.url_for("/v1/b2b/organizations/{organization_id}", data) res = self.sync_client.put(url, data) @@ -356,6 +368,7 @@ async def update_async( email_invites: Optional[str] = None, auth_methods: Optional[str] = None, allowed_auth_methods: Optional[List[str]] = None, + mfa_policy: Optional[str] = None, ) -> UpdateResponse: """Updates an Organization specified by `organization_id`. An Organization must always have at least one auth setting set to either `RESTRICTED` or `ALL_ALLOWED` in order to provision new Members. test @@ -406,6 +419,7 @@ async def update_async( An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -436,6 +450,8 @@ async def update_async( data["auth_methods"] = auth_methods if allowed_auth_methods is not None: data["allowed_auth_methods"] = allowed_auth_methods + if mfa_policy is not None: + data["mfa_policy"] = mfa_policy url = self.api_base.url_for("/v1/b2b/organizations/{organization_id}", data) res = await self.async_client.put(url, data) diff --git a/stytch/b2b/api/organizations_members.py b/stytch/b2b/api/organizations_members.py index 3507b263..faad38b0 100644 --- a/stytch/b2b/api/organizations_members.py +++ b/stytch/b2b/api/organizations_members.py @@ -12,6 +12,7 @@ from stytch.b2b.models.organizations_members import ( CreateResponse, DeletePasswordResponse, + DeletePhoneNumberResponse, DeleteResponse, GetResponse, SearchResponse, @@ -40,6 +41,8 @@ def update( trusted_metadata: Optional[Dict[str, Any]] = None, untrusted_metadata: Optional[Dict[str, Any]] = None, is_breakglass: Optional[bool] = None, + phone_number: Optional[str] = None, + mfa_enrolled: Optional[bool] = None, ) -> UpdateResponse: """Updates a Member specified by `organization_id` and `member_id`. @@ -52,6 +55,8 @@ def update( frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata) for complete field behavior details. - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. + - phone_number: (no documentation yet) + - mfa_enrolled: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -65,6 +70,10 @@ def update( data["untrusted_metadata"] = untrusted_metadata if is_breakglass is not None: data["is_breakglass"] = is_breakglass + if phone_number is not None: + data["phone_number"] = phone_number + if mfa_enrolled is not None: + data["mfa_enrolled"] = mfa_enrolled url = self.api_base.url_for( "/v1/b2b/organizations/{organization_id}/members/{member_id}", data @@ -80,6 +89,8 @@ async def update_async( trusted_metadata: Optional[Dict[str, Any]] = None, untrusted_metadata: Optional[Dict[str, Any]] = None, is_breakglass: Optional[bool] = None, + phone_number: Optional[str] = None, + mfa_enrolled: Optional[bool] = None, ) -> UpdateResponse: """Updates a Member specified by `organization_id` and `member_id`. @@ -92,6 +103,8 @@ async def update_async( frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata) for complete field behavior details. - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. + - phone_number: (no documentation yet) + - mfa_enrolled: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -105,6 +118,10 @@ async def update_async( data["untrusted_metadata"] = untrusted_metadata if is_breakglass is not None: data["is_breakglass"] = is_breakglass + if phone_number is not None: + data["phone_number"] = phone_number + if mfa_enrolled is not None: + data["mfa_enrolled"] = mfa_enrolled url = self.api_base.url_for( "/v1/b2b/organizations/{organization_id}/members/{member_id}", data @@ -156,6 +173,40 @@ async def delete_async( res = await self.async_client.delete(url) return DeleteResponse.from_json(res.response.status, res.json) + def delete_phone_number( + self, + organization_id: str, + member_id: str, + ) -> DeletePhoneNumberResponse: + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/phone_numbers/{member_id}", + data, + ) + res = self.sync_client.delete(url) + return DeletePhoneNumberResponse.from_json(res.response.status_code, res.json) + + async def delete_phone_number_async( + self, + organization_id: str, + member_id: str, + ) -> DeletePhoneNumberResponse: + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/phone_numbers/{member_id}", + data, + ) + res = await self.async_client.delete(url) + return DeletePhoneNumberResponse.from_json(res.response.status, res.json) + def search( self, organization_ids: List[str], @@ -273,6 +324,8 @@ def create( untrusted_metadata: Optional[Dict[str, Any]] = None, create_member_as_pending: Optional[bool] = None, is_breakglass: Optional[bool] = None, + phone_number: Optional[str] = None, + mfa_enrolled: Optional[bool] = None, ) -> CreateResponse: """Creates a Member. An `organization_id` and `email_address` are required. @@ -286,6 +339,8 @@ def create( for complete field behavior details. - create_member_as_pending: Flag for whether or not to save a Member as `pending` or `active` in Stytch. It defaults to false. If true, new Members will be created with status `pending` in Stytch's backend. Their status will remain `pending` and they will continue to receive signup email templates for every Email Magic Link until that Member authenticates and becomes `active`. If false, new Members will be created with status `active`. - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. + - phone_number: (no documentation yet) + - mfa_enrolled: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -301,6 +356,10 @@ def create( data["create_member_as_pending"] = create_member_as_pending if is_breakglass is not None: data["is_breakglass"] = is_breakglass + if phone_number is not None: + data["phone_number"] = phone_number + if mfa_enrolled is not None: + data["mfa_enrolled"] = mfa_enrolled url = self.api_base.url_for( "/v1/b2b/organizations/{organization_id}/members", data @@ -317,6 +376,8 @@ async def create_async( untrusted_metadata: Optional[Dict[str, Any]] = None, create_member_as_pending: Optional[bool] = None, is_breakglass: Optional[bool] = None, + phone_number: Optional[str] = None, + mfa_enrolled: Optional[bool] = None, ) -> CreateResponse: """Creates a Member. An `organization_id` and `email_address` are required. @@ -330,6 +391,8 @@ async def create_async( for complete field behavior details. - create_member_as_pending: Flag for whether or not to save a Member as `pending` or `active` in Stytch. It defaults to false. If true, new Members will be created with status `pending` in Stytch's backend. Their status will remain `pending` and they will continue to receive signup email templates for every Email Magic Link until that Member authenticates and becomes `active`. If false, new Members will be created with status `active`. - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. + - phone_number: (no documentation yet) + - mfa_enrolled: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -345,6 +408,10 @@ async def create_async( data["create_member_as_pending"] = create_member_as_pending if is_breakglass is not None: data["is_breakglass"] = is_breakglass + if phone_number is not None: + data["phone_number"] = phone_number + if mfa_enrolled is not None: + data["mfa_enrolled"] = mfa_enrolled url = self.api_base.url_for( "/v1/b2b/organizations/{organization_id}/members", data diff --git a/stytch/b2b/api/otp.py b/stytch/b2b/api/otp.py new file mode 100644 index 00000000..ec1a79cc --- /dev/null +++ b/stytch/b2b/api/otp.py @@ -0,0 +1,24 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from stytch.b2b.api.otp_sms import Sms +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class OTPs: + def __init__( + self, + api_base: ApiBase, + sync_client: SyncClient, + async_client: AsyncClient, + ) -> None: + self.api_base = api_base + self.sync_client = sync_client + self.async_client = async_client + self.sms = Sms(api_base, sync_client, async_client) diff --git a/stytch/b2b/api/otp_sms.py b/stytch/b2b/api/otp_sms.py new file mode 100644 index 00000000..f377c2bc --- /dev/null +++ b/stytch/b2b/api/otp_sms.py @@ -0,0 +1,137 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import Any, Dict, Optional + +from stytch.b2b.models.otp_sms import ( + AuthenticateResponse, + SendRequestLocale, + SendResponse, +) +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class Sms: + def __init__( + self, + api_base: ApiBase, + sync_client: SyncClient, + async_client: AsyncClient, + ) -> None: + self.api_base = api_base + self.sync_client = sync_client + self.async_client = async_client + + def send( + self, + organization_id: str, + member_id: str, + phone_number: Optional[str] = None, + locale: Optional[SendRequestLocale] = None, + ) -> SendResponse: + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if phone_number is not None: + data["phone_number"] = phone_number + if locale is not None: + data["locale"] = locale.value + + url = self.api_base.url_for("/v1/b2b/otps/sms/send", data) + res = self.sync_client.post(url, data) + return SendResponse.from_json(res.response.status_code, res.json) + + async def send_async( + self, + organization_id: str, + member_id: str, + phone_number: Optional[str] = None, + locale: Optional[SendRequestLocale] = None, + ) -> SendResponse: + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if phone_number is not None: + data["phone_number"] = phone_number + if locale is not None: + data["locale"] = locale.value + + url = self.api_base.url_for("/v1/b2b/otps/sms/send", data) + res = await self.async_client.post(url, data) + return SendResponse.from_json(res.response.status, res.json) + + def authenticate( + self, + organization_id: str, + member_id: str, + code: str, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + set_mfa_enrollment: Optional[str] = None, + ) -> AuthenticateResponse: + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "code": code, + } + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if set_mfa_enrollment is not None: + data["set_mfa_enrollment"] = set_mfa_enrollment + + url = self.api_base.url_for("/v1/b2b/otps/sms/authenticate", data) + res = self.sync_client.post(url, data) + return AuthenticateResponse.from_json(res.response.status_code, res.json) + + async def authenticate_async( + self, + organization_id: str, + member_id: str, + code: str, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + set_mfa_enrollment: Optional[str] = None, + ) -> AuthenticateResponse: + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "code": code, + } + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if set_mfa_enrollment is not None: + data["set_mfa_enrollment"] = set_mfa_enrollment + + url = self.api_base.url_for("/v1/b2b/otps/sms/authenticate", data) + res = await self.async_client.post(url, data) + return AuthenticateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/passwords.py b/stytch/b2b/api/passwords.py index 74c60a01..dfd47209 100644 --- a/stytch/b2b/api/passwords.py +++ b/stytch/b2b/api/passwords.py @@ -12,6 +12,7 @@ from stytch.b2b.api.passwords_existing_password import ExistingPassword from stytch.b2b.api.passwords_session import Sessions from stytch.b2b.models.passwords import ( + AuthenticateRequestLocale, AuthenticateResponse, MigrateRequestHashType, MigrateResponse, @@ -231,6 +232,7 @@ def authenticate( session_duration_minutes: Optional[int] = None, session_jwt: Optional[str] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, ) -> AuthenticateResponse: """Authenticate a member with their email address and password. This endpoint verifies that the member has a password currently set, and that the entered password is correct. There are two instances where the endpoint will return a reset_password error even if they enter their previous password: * The member’s credentials appeared in the HaveIBeenPwned dataset. @@ -259,6 +261,7 @@ def authenticate( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -273,6 +276,8 @@ def authenticate( data["session_jwt"] = session_jwt if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/passwords/authenticate", data) res = self.sync_client.post(url, data) @@ -287,6 +292,7 @@ async def authenticate_async( session_duration_minutes: Optional[int] = None, session_jwt: Optional[str] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, ) -> AuthenticateResponse: """Authenticate a member with their email address and password. This endpoint verifies that the member has a password currently set, and that the entered password is correct. There are two instances where the endpoint will return a reset_password error even if they enter their previous password: * The member’s credentials appeared in the HaveIBeenPwned dataset. @@ -315,6 +321,7 @@ async def authenticate_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -329,6 +336,8 @@ async def authenticate_async( data["session_jwt"] = session_jwt if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/passwords/authenticate", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/api/passwords_email.py b/stytch/b2b/api/passwords_email.py index 1d728f4f..80c6c54c 100644 --- a/stytch/b2b/api/passwords_email.py +++ b/stytch/b2b/api/passwords_email.py @@ -9,6 +9,7 @@ from typing import Any, Dict, Optional from stytch.b2b.models.passwords_email import ( + ResetRequestLocale, ResetResponse, ResetStartRequestLocale, ResetStartResponse, @@ -161,6 +162,7 @@ def reset( session_jwt: Optional[str] = None, code_verifier: Optional[str] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ResetRequestLocale] = None, ) -> ResetResponse: """Reset the member's password and authenticate them. This endpoint checks that the password reset token is valid, hasn’t expired, or already been used. @@ -190,6 +192,7 @@ def reset( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "password_reset_token": password_reset_token, @@ -205,6 +208,8 @@ def reset( data["code_verifier"] = code_verifier if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/passwords/email/reset", data) res = self.sync_client.post(url, data) @@ -219,6 +224,7 @@ async def reset_async( session_jwt: Optional[str] = None, code_verifier: Optional[str] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ResetRequestLocale] = None, ) -> ResetResponse: """Reset the member's password and authenticate them. This endpoint checks that the password reset token is valid, hasn’t expired, or already been used. @@ -248,6 +254,7 @@ async def reset_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "password_reset_token": password_reset_token, @@ -263,6 +270,8 @@ async def reset_async( data["code_verifier"] = code_verifier if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/passwords/email/reset", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/api/passwords_existing_password.py b/stytch/b2b/api/passwords_existing_password.py index b96091a4..cec87fcc 100644 --- a/stytch/b2b/api/passwords_existing_password.py +++ b/stytch/b2b/api/passwords_existing_password.py @@ -8,7 +8,10 @@ from typing import Any, Dict, Optional -from stytch.b2b.models.passwords_existing_password import ResetResponse +from stytch.b2b.models.passwords_existing_password import ( + ResetRequestLocale, + ResetResponse, +) from stytch.core.api_base import ApiBase from stytch.core.http.client import AsyncClient, SyncClient @@ -34,6 +37,7 @@ def reset( session_duration_minutes: Optional[int] = None, session_jwt: Optional[str] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ResetRequestLocale] = None, ) -> ResetResponse: """Reset the member’s password using their existing password. @@ -64,6 +68,7 @@ def reset( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "email_address": email_address, @@ -79,6 +84,8 @@ def reset( data["session_jwt"] = session_jwt if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/passwords/existing_password/reset", data) res = self.sync_client.post(url, data) @@ -94,6 +101,7 @@ async def reset_async( session_duration_minutes: Optional[int] = None, session_jwt: Optional[str] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ResetRequestLocale] = None, ) -> ResetResponse: """Reset the member’s password using their existing password. @@ -124,6 +132,7 @@ async def reset_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "email_address": email_address, @@ -139,6 +148,8 @@ async def reset_async( data["session_jwt"] = session_jwt if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/passwords/existing_password/reset", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/api/sessions.py b/stytch/b2b/api/sessions.py index e2691903..e9598577 100644 --- a/stytch/b2b/api/sessions.py +++ b/stytch/b2b/api/sessions.py @@ -10,6 +10,7 @@ from stytch.b2b.models.sessions import ( AuthenticateResponse, + ExchangeRequestLocale, ExchangeResponse, GetJWKSResponse, GetResponse, @@ -221,6 +222,7 @@ def exchange( session_jwt: Optional[str] = None, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ExchangeRequestLocale] = None, ) -> ExchangeResponse: """Use this endpoint to exchange a Member's existing session for another session in a different Organization. This can be used to accept an invite, but not to create a new member via domain matching. @@ -244,6 +246,7 @@ def exchange( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -256,6 +259,8 @@ def exchange( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/sessions/exchange", data) res = self.sync_client.post(url, data) @@ -268,6 +273,7 @@ async def exchange_async( session_jwt: Optional[str] = None, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[ExchangeRequestLocale] = None, ) -> ExchangeResponse: """Use this endpoint to exchange a Member's existing session for another session in a different Organization. This can be used to accept an invite, but not to create a new member via domain matching. @@ -291,6 +297,7 @@ async def exchange_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "organization_id": organization_id, @@ -303,6 +310,8 @@ async def exchange_async( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/sessions/exchange", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/api/sso.py b/stytch/b2b/api/sso.py index 34ed207f..068e4692 100644 --- a/stytch/b2b/api/sso.py +++ b/stytch/b2b/api/sso.py @@ -11,6 +11,7 @@ from stytch.b2b.api.sso_oidc import OIDC from stytch.b2b.api.sso_saml import SAML from stytch.b2b.models.sso import ( + AuthenticateRequestLocale, AuthenticateResponse, DeleteConnectionResponse, GetConnectionsResponse, @@ -118,6 +119,7 @@ def authenticate( session_jwt: Optional[str] = None, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, ) -> AuthenticateResponse: """Authenticate a user given a token. This endpoint verifies that the user completed the SSO Authentication flow by verifying that the token is valid and hasn't expired. @@ -144,6 +146,7 @@ def authenticate( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "sso_token": sso_token, @@ -158,6 +161,8 @@ def authenticate( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/sso/authenticate", data) res = self.sync_client.post(url, data) @@ -171,6 +176,7 @@ async def authenticate_async( session_jwt: Optional[str] = None, session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, ) -> AuthenticateResponse: """Authenticate a user given a token. This endpoint verifies that the user completed the SSO Authentication flow by verifying that the token is valid and hasn't expired. @@ -197,6 +203,7 @@ async def authenticate_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. + - locale: (no documentation yet) """ # noqa data: Dict[str, Any] = { "sso_token": sso_token, @@ -211,6 +218,8 @@ async def authenticate_async( data["session_duration_minutes"] = session_duration_minutes if session_custom_claims is not None: data["session_custom_claims"] = session_custom_claims + if locale is not None: + data["locale"] = locale.value url = self.api_base.url_for("/v1/b2b/sso/authenticate", data) res = await self.async_client.post(url, data) diff --git a/stytch/b2b/client.py b/stytch/b2b/client.py index 9521a354..53bed3b7 100644 --- a/stytch/b2b/client.py +++ b/stytch/b2b/client.py @@ -9,7 +9,9 @@ from stytch.b2b.api.discovery import Discovery from stytch.b2b.api.magic_links import MagicLinks +from stytch.b2b.api.oauth import OAuth from stytch.b2b.api.organizations import Organizations +from stytch.b2b.api.otp import OTPs from stytch.b2b.api.passwords import Passwords from stytch.b2b.api.sessions import Sessions from stytch.b2b.api.sso import SSO @@ -32,13 +34,15 @@ def __init__( ): super().__init__(project_id, secret, environment, suppress_warnings) - self.organizations = Organizations( - self.api_base, self.sync_client, self.async_client - ) - self.sessions = Sessions(self.api_base, self.sync_client, self.async_client) self.discovery = Discovery(self.api_base, self.sync_client, self.async_client) self.magic_links = MagicLinks( self.api_base, self.sync_client, self.async_client ) + self.oauth = OAuth(self.api_base, self.sync_client, self.async_client) + self.otps = OTPs(self.api_base, self.sync_client, self.async_client) + self.organizations = Organizations( + self.api_base, self.sync_client, self.async_client + ) self.passwords = Passwords(self.api_base, self.sync_client, self.async_client) self.sso = SSO(self.api_base, self.sync_client, self.async_client) + self.sessions = Sessions(self.api_base, self.sync_client, self.async_client) diff --git a/stytch/b2b/models/discovery.py b/stytch/b2b/models/discovery.py index f613ea1a..aa2ace7d 100644 --- a/stytch/b2b/models/discovery.py +++ b/stytch/b2b/models/discovery.py @@ -6,10 +6,11 @@ from __future__ import annotations -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional import pydantic +from stytch.b2b.models.mfa import MfaRequired from stytch.b2b.models.organizations import Member, Organization @@ -26,6 +27,10 @@ class Membership(pydantic.BaseModel): member: Optional[Member] = None +class PrimaryRequired(pydantic.BaseModel): + allowed_auth_methods: List[str] + + class DiscoveredOrganization(pydantic.BaseModel): """ Fields: @@ -33,8 +38,12 @@ class DiscoveredOrganization(pydantic.BaseModel): If not, the member needs to perform additional authentication before logging in - such as password or SSO auth. - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). - membership: Information about the membership. + - primary_required: (no documentation yet) + - mfa_required: (no documentation yet) """ # noqa member_authenticated: bool organization: Optional[Organization] = None membership: Optional[Membership] = None + primary_required: Optional[PrimaryRequired] = None + mfa_required: Optional[MfaRequired] = None diff --git a/stytch/b2b/models/discovery_intermediate_sessions.py b/stytch/b2b/models/discovery_intermediate_sessions.py index c9b757b3..d6e18fd0 100644 --- a/stytch/b2b/models/discovery_intermediate_sessions.py +++ b/stytch/b2b/models/discovery_intermediate_sessions.py @@ -6,6 +6,7 @@ from __future__ import annotations +import enum from typing import Optional from stytch.b2b.models.organizations import Member, Organization @@ -13,6 +14,12 @@ from stytch.core.response_base import ResponseBase +class ExchangeRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class ExchangeResponse(ResponseBase): """Response type for `IntermediateSessions.exchange`. Fields: diff --git a/stytch/b2b/models/magic_links.py b/stytch/b2b/models/magic_links.py index 0603a0ce..3cde30c1 100644 --- a/stytch/b2b/models/magic_links.py +++ b/stytch/b2b/models/magic_links.py @@ -6,11 +6,19 @@ from __future__ import annotations +import enum + from stytch.b2b.models.organizations import Member, Organization from stytch.b2b.models.sessions import MemberSession from stytch.core.response_base import ResponseBase +class AuthenticateRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class AuthenticateResponse(ResponseBase): """Response type for `MagicLinks.authenticate`. Fields: diff --git a/stytch/b2b/models/mfa.py b/stytch/b2b/models/mfa.py new file mode 100644 index 00000000..9478b966 --- /dev/null +++ b/stytch/b2b/models/mfa.py @@ -0,0 +1,20 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import Optional + +import pydantic + + +class MemberOptions(pydantic.BaseModel): + phone_number: str + + +class MfaRequired(pydantic.BaseModel): + member_options: Optional[MemberOptions] = None + secondary_auth_initiated: Optional[str] = None diff --git a/stytch/b2b/models/oauth.py b/stytch/b2b/models/oauth.py new file mode 100644 index 00000000..8ecb47ce --- /dev/null +++ b/stytch/b2b/models/oauth.py @@ -0,0 +1,71 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +import datetime +import enum +from typing import List, Optional + +import pydantic + +from stytch.b2b.models.organizations import Member, Organization +from stytch.b2b.models.sessions import MemberSession +from stytch.core.response_base import ResponseBase + + +class AuthenticateRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + +class ProviderValues(pydantic.BaseModel): + """ + Fields: + - access_token: The `access_token` that you may use to access the User's data in the provider's API. + - scopes: The OAuth scopes included for a given provider. See each provider's section above to see which scopes are included by default and how to add custom scopes. + - refresh_token: The `refresh_token` that you may use to refresh a User's session within the provider's API. + - expires_at: (no documentation yet) + - id_token: The `id_token` returned by the OAuth provider. ID Tokens are JWTs that contain structured information about a user. The exact content of each ID Token varies from provider to provider. ID Tokens are returned from OAuth providers that conform to the [OpenID Connect](https://openid.net/foundation/) specification, which is based on OAuth. + """ # noqa + + access_token: str + scopes: List[str] + refresh_token: Optional[str] = None + expires_at: Optional[datetime.datetime] = None + id_token: Optional[str] = None + + +class AuthenticateResponse(ResponseBase): + """Response type for `OAuth.authenticate`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - provider_subject: (no documentation yet) + - provider_type: (no documentation yet) + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object). + - 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. + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - reset_sessions: (no documentation yet) + - member_session: The [Session object](https://stytch.com/docs/b2b/api/session-object). + - provider_values: The `provider_values` object lists relevant identifiers, values, and scopes for a given OAuth provider. For example this object will include a provider's `access_token` that you can use to access the provider's API for a given user. + + Note that these values will vary based on the OAuth provider in question, e.g. `id_token` is only returned by Microsoft. + """ # noqa + + member_id: str + provider_subject: str + provider_type: str + session_token: str + session_jwt: str + member: Member + organization_id: str + organization: Organization + reset_sessions: bool + member_session: Optional[MemberSession] = None + provider_values: Optional[ProviderValues] = None diff --git a/stytch/b2b/models/oauth_discovery.py b/stytch/b2b/models/oauth_discovery.py new file mode 100644 index 00000000..5912d2c4 --- /dev/null +++ b/stytch/b2b/models/oauth_discovery.py @@ -0,0 +1,36 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import List + +from stytch.b2b.models.discovery import DiscoveredOrganization +from stytch.core.response_base import ResponseBase + + +class AuthenticateResponse(ResponseBase): + """Response type for `Discovery.authenticate`. + Fields: + - intermediate_session_token: The Intermediate Session Token. This token does not belong to a specific instance of a member, but may be exchanged for an existing Member Session or used to create a new organization. + - email_address: The email address. + - discovered_organizations: An array of `discovered_organization` objects tied to the `intermediate_session_token`, `session_token`, or `session_jwt`. See the [Discovered Organization Object](https://stytch.com/docs/b2b/api/discovered-organization-object) for complete details. + + Note that Organizations will only appear here under any of the following conditions: + 1. The end user is already a Member of the Organization. + 2. The end user is invited to the Organization. + 3. The end user can join the Organization because: + + a) The Organization allows JIT provisioning. + + b) The Organizations' allowed domains list contains the Member's email domain. + + c) The Organization has at least one other Member with a verified email address with the same domain as the end user (to prevent phishing attacks). + """ # noqa + + intermediate_session_token: str + email_address: str + discovered_organizations: List[DiscoveredOrganization] diff --git a/stytch/b2b/models/organizations.py b/stytch/b2b/models/organizations.py index 794a6aca..4fb46139 100644 --- a/stytch/b2b/models/organizations.py +++ b/stytch/b2b/models/organizations.py @@ -93,6 +93,7 @@ class Organization(pydantic.BaseModel): An array of allowed authentication methods. This list is enforced when `auth_methods` is set to `RESTRICTED`. The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. + - mfa_policy: (no documentation yet) - trusted_metadata: An arbitrary JSON object for storing application-specific data or identity-provider-specific data. - sso_default_connection_id: The default connection used for SSO when there are multiple active connections. """ # noqa @@ -109,6 +110,7 @@ class Organization(pydantic.BaseModel): email_invites: str auth_methods: str allowed_auth_methods: List[str] + mfa_policy: str trusted_metadata: Optional[Dict[str, Any]] = None sso_default_connection_id: Optional[str] = None @@ -151,6 +153,8 @@ class Member(pydantic.BaseModel): - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. - member_password_id: Globally unique UUID that identifies a Member's password. - oauth_registrations: A list of OAuth registrations for this member. + - mfa_enrolled: (no documentation yet) + - mfa_phone_number: (no documentation yet) - trusted_metadata: An arbitrary JSON object for storing application-specific data or identity-provider-specific data. - untrusted_metadata: An arbitrary JSON object of application-specific data. These fields can be edited directly by the frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata) @@ -166,6 +170,8 @@ class Member(pydantic.BaseModel): is_breakglass: bool member_password_id: str oauth_registrations: List[OAuthRegistration] + mfa_enrolled: bool + mfa_phone_number: str trusted_metadata: Optional[Dict[str, Any]] = None untrusted_metadata: Optional[Dict[str, Any]] = None diff --git a/stytch/b2b/models/organizations_members.py b/stytch/b2b/models/organizations_members.py index 3946cedb..13fd24d8 100644 --- a/stytch/b2b/models/organizations_members.py +++ b/stytch/b2b/models/organizations_members.py @@ -38,6 +38,12 @@ class DeletePasswordResponse(ResponseBase): organization: Organization +class DeletePhoneNumberResponse(ResponseBase): + member_id: str + member: Member + organization: Organization + + class DeleteResponse(ResponseBase): """Response type for `Members.delete`. Fields: diff --git a/stytch/b2b/models/otp_sms.py b/stytch/b2b/models/otp_sms.py new file mode 100644 index 00000000..f2fae041 --- /dev/null +++ b/stytch/b2b/models/otp_sms.py @@ -0,0 +1,35 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +import enum +from typing import Optional + +from stytch.b2b.models.organizations import Member, Organization +from stytch.b2b.models.sessions import MemberSession +from stytch.core.response_base import ResponseBase + + +class SendRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + +class AuthenticateResponse(ResponseBase): + member_id: str + member: Member + organization: Organization + session_token: str + session_jwt: str + member_session: Optional[MemberSession] = None + + +class SendResponse(ResponseBase): + member_id: str + member: Member + organization: Organization diff --git a/stytch/b2b/models/passwords.py b/stytch/b2b/models/passwords.py index e7628be7..e3051b27 100644 --- a/stytch/b2b/models/passwords.py +++ b/stytch/b2b/models/passwords.py @@ -16,6 +16,12 @@ from stytch.core.response_base import ResponseBase +class AuthenticateRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class MigrateRequestHashType(enum.Enum): BCRYPT = "bcrypt" MD_5 = "md_5" diff --git a/stytch/b2b/models/passwords_email.py b/stytch/b2b/models/passwords_email.py index b395a1d2..100d3875 100644 --- a/stytch/b2b/models/passwords_email.py +++ b/stytch/b2b/models/passwords_email.py @@ -14,6 +14,12 @@ from stytch.core.response_base import ResponseBase +class ResetRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class ResetStartRequestLocale(enum.Enum): EN = "en" ES = "es" diff --git a/stytch/b2b/models/passwords_existing_password.py b/stytch/b2b/models/passwords_existing_password.py index d1528333..011c9ce1 100644 --- a/stytch/b2b/models/passwords_existing_password.py +++ b/stytch/b2b/models/passwords_existing_password.py @@ -6,6 +6,7 @@ from __future__ import annotations +import enum from typing import Optional from stytch.b2b.models.organizations import Member, Organization @@ -13,6 +14,12 @@ from stytch.core.response_base import ResponseBase +class ResetRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class ResetResponse(ResponseBase): """Response type for `ExistingPassword.reset`. Fields: diff --git a/stytch/b2b/models/sessions.py b/stytch/b2b/models/sessions.py index f402c57f..e9cbf6d6 100644 --- a/stytch/b2b/models/sessions.py +++ b/stytch/b2b/models/sessions.py @@ -7,7 +7,8 @@ from __future__ import annotations import datetime -from typing import Any, Dict, List, Optional +import enum +from typing import Any, Dict, List import pydantic @@ -16,6 +17,12 @@ from stytch.core.response_base import ResponseBase +class ExchangeRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class MemberSession(pydantic.BaseModel): """ Fields: @@ -25,8 +32,8 @@ class MemberSession(pydantic.BaseModel): - last_accessed_at: The timestamp when the Session was last accessed. Values conform to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`. - expires_at: The timestamp when the Session expires. Values conform to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`. - authentication_factors: An array of different authentication factors that have initiated a Session. - - 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. - custom_claims: The custom claims map for a Session. Claims can be added to a session during a Sessions authenticate call. + - 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. """ # noqa member_session_id: str @@ -35,8 +42,8 @@ class MemberSession(pydantic.BaseModel): last_accessed_at: datetime.datetime expires_at: datetime.datetime authentication_factors: List[AuthenticationFactor] + custom_claims: Dict[str, Any] organization_id: str - custom_claims: Optional[Dict[str, Any]] = None class AuthenticateResponse(ResponseBase): diff --git a/stytch/b2b/models/sso.py b/stytch/b2b/models/sso.py index 2e143f93..be3c3be3 100644 --- a/stytch/b2b/models/sso.py +++ b/stytch/b2b/models/sso.py @@ -7,6 +7,7 @@ from __future__ import annotations import datetime +import enum from typing import Any, Dict, List, Optional import pydantic @@ -16,6 +17,12 @@ from stytch.core.response_base import ResponseBase +class AuthenticateRequestLocale(enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + class OIDCConnection(pydantic.BaseModel): organization_id: str connection_id: str diff --git a/stytch/consumer/client.py b/stytch/consumer/client.py index 66d53e57..468d0ad4 100644 --- a/stytch/consumer/client.py +++ b/stytch/consumer/client.py @@ -35,16 +35,16 @@ def __init__( ): super().__init__(project_id, secret, environment, suppress_warnings) - self.users = Users(self.api_base, self.sync_client, self.async_client) - self.sessions = Sessions(self.api_base, self.sync_client, self.async_client) self.crypto_wallets = CryptoWallets( self.api_base, self.sync_client, self.async_client ) self.magic_links = MagicLinks( self.api_base, self.sync_client, self.async_client ) - self.passwords = Passwords(self.api_base, self.sync_client, self.async_client) self.oauth = OAuth(self.api_base, self.sync_client, self.async_client) self.otps = OTPs(self.api_base, self.sync_client, self.async_client) + self.passwords = Passwords(self.api_base, self.sync_client, self.async_client) + self.sessions = Sessions(self.api_base, self.sync_client, self.async_client) self.totps = TOTPs(self.api_base, self.sync_client, self.async_client) + self.users = Users(self.api_base, self.sync_client, self.async_client) self.webauthn = WebAuthn(self.api_base, self.sync_client, self.async_client) diff --git a/stytch/consumer/models/sessions.py b/stytch/consumer/models/sessions.py index a6289f49..a04a7376 100644 --- a/stytch/consumer/models/sessions.py +++ b/stytch/consumer/models/sessions.py @@ -52,6 +52,7 @@ class AuthenticationFactorDeliveryMethod(enum.Enum): SSO_SAML = "sso_saml" SSO_OIDC = "sso_oidc" OAUTH_SALESFORCE = "oauth_salesforce" + OAUTH_YAHOO = "oauth_yahoo" class AuthenticationFactorType(enum.Enum): @@ -260,6 +261,12 @@ class WebAuthnFactor(pydantic.BaseModel): user_agent: str +class YahooOAuthFactor(pydantic.BaseModel): + id: str + email_id: str + provider_subject: str + + class AuthenticationFactor(pydantic.BaseModel): type: AuthenticationFactorType delivery_method: AuthenticationFactorDeliveryMethod @@ -298,6 +305,7 @@ class AuthenticationFactor(pydantic.BaseModel): saml_sso_factor: Optional[SAMLSSOFactor] = None oidc_sso_factor: Optional[OIDCSSOFactor] = None salesforce_oauth_factor: Optional[SalesforceOAuthFactor] = None + yahoo_oauth_factor: Optional[YahooOAuthFactor] = None class Config: use_enum_values = True diff --git a/stytch/version.py b/stytch/version.py index b2ea6c25..54ccb5d2 100644 --- a/stytch/version.py +++ b/stytch/version.py @@ -1 +1 @@ -__version__ = "7.0.2" +__version__ = "7.1.0"