diff --git a/stytch/b2b/api/discovery_organizations.py b/stytch/b2b/api/discovery_organizations.py index 21b77e8..608290b 100644 --- a/stytch/b2b/api/discovery_organizations.py +++ b/stytch/b2b/api/discovery_organizations.py @@ -142,7 +142,7 @@ def create( `NOT_ALLOWED` – disable JIT provisioning by OAuth Tenant. - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -310,7 +310,7 @@ async def create_async( `NOT_ALLOWED` – disable JIT provisioning by OAuth Tenant. - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { diff --git a/stytch/b2b/api/oauth_discovery.py b/stytch/b2b/api/oauth_discovery.py index 7977ff9..e0c4402 100644 --- a/stytch/b2b/api/oauth_discovery.py +++ b/stytch/b2b/api/oauth_discovery.py @@ -30,7 +30,8 @@ def authenticate( session_custom_claims: Optional[Dict[str, Any]] = None, pkce_code_verifier: Optional[str] = None, ) -> AuthenticateResponse: - """Authenticates the Discovery 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. + """Authenticates the Discovery 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. @@ -68,7 +69,8 @@ async def authenticate_async( session_custom_claims: Optional[Dict[str, Any]] = None, pkce_code_verifier: Optional[str] = None, ) -> AuthenticateResponse: - """Authenticates the Discovery 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. + """Authenticates the Discovery 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. diff --git a/stytch/b2b/api/organizations.py b/stytch/b2b/api/organizations.py index 68f6493..a9da8c7 100644 --- a/stytch/b2b/api/organizations.py +++ b/stytch/b2b/api/organizations.py @@ -130,7 +130,7 @@ def create( `NOT_ALLOWED` – disable JIT provisioning by OAuth Tenant. - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -266,7 +266,7 @@ async def create_async( `NOT_ALLOWED` – disable JIT provisioning by OAuth Tenant. - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -478,7 +478,7 @@ def update( If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.oauth-tenant-jit-provisioning` action on the `stytch.organization` Resource. - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.allowed-oauth-tenants` action on the `stytch.organization` Resource. """ # noqa @@ -666,7 +666,7 @@ async def update_async( If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.oauth-tenant-jit-provisioning` action on the `stytch.organization` Resource. - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.allowed-oauth-tenants` action on the `stytch.organization` Resource. """ # noqa diff --git a/stytch/b2b/api/organizations_members.py b/stytch/b2b/api/organizations_members.py index cf4fd2b..239fe21 100644 --- a/stytch/b2b/api/organizations_members.py +++ b/stytch/b2b/api/organizations_members.py @@ -655,6 +655,15 @@ def oidc_providers( member_id: str, include_refresh_token: Optional[bool] = None, ) -> OIDCProvidersResponse: + """Retrieve the saved OIDC access tokens and ID tokens for a member. After a successful OIDC login, Stytch will save the + issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the + access token automatically. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { "organization_id": organization_id, @@ -676,6 +685,15 @@ async def oidc_providers_async( member_id: str, include_refresh_token: Optional[bool] = None, ) -> OIDCProvidersResponse: + """Retrieve the saved OIDC access tokens and ID tokens for a member. After a successful OIDC login, Stytch will save the + issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the + access token automatically. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { "organization_id": organization_id, diff --git a/stytch/b2b/api/organizations_members_oauth_providers.py b/stytch/b2b/api/organizations_members_oauth_providers.py index 6d11b1f..5c835ad 100644 --- a/stytch/b2b/api/organizations_members_oauth_providers.py +++ b/stytch/b2b/api/organizations_members_oauth_providers.py @@ -9,8 +9,11 @@ from typing import Any, Dict, Optional from stytch.b2b.models.organizations_members_oauth_providers import ( + GithubResponse, GoogleResponse, + HubspotResponse, MicrosoftResponse, + SlackResponse, ) from stytch.core.api_base import ApiBase from stytch.core.http.client import AsyncClient, SyncClient @@ -155,3 +158,173 @@ async def microsoft_async( ) res = await self.async_client.get(url, data, headers) return MicrosoftResponse.from_json(res.response.status, res.json) + + def slack( + self, + organization_id: str, + member_id: str, + ) -> SlackResponse: + """Retrieve the saved Slack access token and ID token for a member. After a successful OAuth login, Stytch will save the + issued access token and ID token from the identity provider. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/oauth_providers/slack", + data, + ) + res = self.sync_client.get(url, data, headers) + return SlackResponse.from_json(res.response.status_code, res.json) + + async def slack_async( + self, + organization_id: str, + member_id: str, + ) -> SlackResponse: + """Retrieve the saved Slack access token and ID token for a member. After a successful OAuth login, Stytch will save the + issued access token and ID token from the identity provider. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/oauth_providers/slack", + data, + ) + res = await self.async_client.get(url, data, headers) + return SlackResponse.from_json(res.response.status, res.json) + + def hubspot( + self, + organization_id: str, + member_id: str, + include_refresh_token: Optional[bool] = None, + ) -> HubspotResponse: + """Retrieve the saved Hubspot access token and ID token for a member. After a successful OAuth login, Stytch will save the + issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the + access token automatically. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if include_refresh_token is not None: + data["include_refresh_token"] = include_refresh_token + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/oauth_providers/hubspot", + data, + ) + res = self.sync_client.get(url, data, headers) + return HubspotResponse.from_json(res.response.status_code, res.json) + + async def hubspot_async( + self, + organization_id: str, + member_id: str, + include_refresh_token: Optional[bool] = None, + ) -> HubspotResponse: + """Retrieve the saved Hubspot access token and ID token for a member. After a successful OAuth login, Stytch will save the + issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the + access token automatically. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if include_refresh_token is not None: + data["include_refresh_token"] = include_refresh_token + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/oauth_providers/hubspot", + data, + ) + res = await self.async_client.get(url, data, headers) + return HubspotResponse.from_json(res.response.status, res.json) + + def github( + self, + organization_id: str, + member_id: str, + include_refresh_token: Optional[bool] = None, + ) -> GithubResponse: + """Retrieve the saved GitHub access token for a Member. After a successful OAuth login, Stytch will save the + issued access token from the identity provider. GitHub does not issue refresh tokens, but will invalidate access + tokens after very long periods of inactivity. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if include_refresh_token is not None: + data["include_refresh_token"] = include_refresh_token + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/oauth_providers/github", + data, + ) + res = self.sync_client.get(url, data, headers) + return GithubResponse.from_json(res.response.status_code, res.json) + + async def github_async( + self, + organization_id: str, + member_id: str, + include_refresh_token: Optional[bool] = None, + ) -> GithubResponse: + """Retrieve the saved GitHub access token for a Member. After a successful OAuth login, Stytch will save the + issued access token from the identity provider. GitHub does not issue refresh tokens, but will invalidate access + tokens after very long periods of inactivity. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if include_refresh_token is not None: + data["include_refresh_token"] = include_refresh_token + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/oauth_providers/github", + data, + ) + res = await self.async_client.get(url, data, headers) + return GithubResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/otp_sms.py b/stytch/b2b/api/otp_sms.py index 8ccba04..2d5b873 100644 --- a/stytch/b2b/api/otp_sms.py +++ b/stytch/b2b/api/otp_sms.py @@ -42,7 +42,7 @@ def send( An error will be thrown if the Member already has a phone number and the provided `mfa_phone_number` does not match the existing one. - Note that sending another OTP code before the first has expired will invalidate the first code. + OTP codes expire after two minutes. Note that sending another OTP code before the first has expired will invalidate the first code. If a Member has a phone number and is enrolled in MFA, then after a successful primary authentication event (e.g. [email magic link](https://stytch.com/docs/b2b/api/authenticate-magic-link) or [SSO](https://stytch.com/docs/b2b/api/sso-authenticate) login is complete), an SMS OTP will automatically be sent to their phone number. In that case, this endpoint should only be used for subsequent authentication events, such as prompting a Member for an OTP again after a period of inactivity. @@ -106,7 +106,7 @@ async def send_async( An error will be thrown if the Member already has a phone number and the provided `mfa_phone_number` does not match the existing one. - Note that sending another OTP code before the first has expired will invalidate the first code. + OTP codes expire after two minutes. Note that sending another OTP code before the first has expired will invalidate the first code. If a Member has a phone number and is enrolled in MFA, then after a successful primary authentication event (e.g. [email magic link](https://stytch.com/docs/b2b/api/authenticate-magic-link) or [SSO](https://stytch.com/docs/b2b/api/sso-authenticate) login is complete), an SMS OTP will automatically be sent to their phone number. In that case, this endpoint should only be used for subsequent authentication events, such as prompting a Member for an OTP again after a period of inactivity. @@ -168,7 +168,9 @@ def authenticate( ) -> AuthenticateResponse: """SMS OTPs may not be used as a primary authentication mechanism. They can be used to complete an MFA requirement, or they can be used as a step-up factor to be added to an existing session. - This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated. + This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. OTP codes expire after two minutes. + + A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated. Exactly one of `intermediate_session_token`, `session_token`, or `session_jwt` must be provided in the request. If an intermediate session token is provided, this operation will consume it. @@ -252,7 +254,9 @@ async def authenticate_async( ) -> AuthenticateResponse: """SMS OTPs may not be used as a primary authentication mechanism. They can be used to complete an MFA requirement, or they can be used as a step-up factor to be added to an existing session. - This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated. + This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. OTP codes expire after two minutes. + + A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated. Exactly one of `intermediate_session_token`, `session_token`, or `session_jwt` must be provided in the request. If an intermediate session token is provided, this operation will consume it. diff --git a/stytch/b2b/api/passwords.py b/stytch/b2b/api/passwords.py index 9c9046d..acada96 100644 --- a/stytch/b2b/api/passwords.py +++ b/stytch/b2b/api/passwords.py @@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Union +from stytch.b2b.api.passwords_discovery import Discovery from stytch.b2b.api.passwords_email import Email from stytch.b2b.api.passwords_existing_password import ExistingPassword from stytch.b2b.api.passwords_session import Sessions @@ -51,6 +52,11 @@ def __init__( sync_client=self.sync_client, async_client=self.async_client, ) + self.discovery = Discovery( + api_base=self.api_base, + sync_client=self.sync_client, + async_client=self.async_client, + ) def strength_check( self, diff --git a/stytch/b2b/api/passwords_discovery.py b/stytch/b2b/api/passwords_discovery.py new file mode 100644 index 0000000..95495ad --- /dev/null +++ b/stytch/b2b/api/passwords_discovery.py @@ -0,0 +1,78 @@ +# !!! +# 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 + +from stytch.b2b.api.passwords_discovery_email import Email +from stytch.b2b.models.passwords_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 + self.email = Email( + api_base=self.api_base, + sync_client=self.sync_client, + async_client=self.async_client, + ) + + def authenticate( + self, + email_address: str, + password: str, + ) -> AuthenticateResponse: + """Authenticate an email/password combination in the discovery flow. This authenticate flow is only valid for cross-org passwords use cases, and is not tied to a specific organization. + + If you have breach detection during authentication enabled in your [password strength policy](https://stytch.com/docs/b2b/guides/passwords/strength-policies) and the member's credentials have appeared in the HaveIBeenPwned dataset, this endpoint will return a `member_reset_password` error even if the member enters a correct password. We force a password reset in this case to ensure that the member is the legitimate owner of the email address and not a malicious actor abusing the compromised credentials. + + If successful, this endpoint will create a new intermediate session and return a list of discovered organizations that can be session exchanged into. + + Fields: + - email_address: The email address of the Member. + - password: The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + "password": password, + } + + url = self.api_base.url_for("/v1/b2b/passwords/discovery/authenticate", data) + res = self.sync_client.post(url, data, headers) + return AuthenticateResponse.from_json(res.response.status_code, res.json) + + async def authenticate_async( + self, + email_address: str, + password: str, + ) -> AuthenticateResponse: + """Authenticate an email/password combination in the discovery flow. This authenticate flow is only valid for cross-org passwords use cases, and is not tied to a specific organization. + + If you have breach detection during authentication enabled in your [password strength policy](https://stytch.com/docs/b2b/guides/passwords/strength-policies) and the member's credentials have appeared in the HaveIBeenPwned dataset, this endpoint will return a `member_reset_password` error even if the member enters a correct password. We force a password reset in this case to ensure that the member is the legitimate owner of the email address and not a malicious actor abusing the compromised credentials. + + If successful, this endpoint will create a new intermediate session and return a list of discovered organizations that can be session exchanged into. + + Fields: + - email_address: The email address of the Member. + - password: The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + "password": password, + } + + url = self.api_base.url_for("/v1/b2b/passwords/discovery/authenticate", data) + res = await self.async_client.post(url, data, headers) + return AuthenticateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/passwords_discovery_email.py b/stytch/b2b/api/passwords_discovery_email.py new file mode 100644 index 0000000..aebf7b2 --- /dev/null +++ b/stytch/b2b/api/passwords_discovery_email.py @@ -0,0 +1,205 @@ +# !!! +# 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.passwords_discovery_email import ( + ResetResponse, + ResetStartResponse, +) +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class Email: + 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 reset_start( + self, + email_address: str, + reset_password_redirect_url: Optional[str] = None, + discovery_redirect_url: Optional[str] = None, + reset_password_template_id: Optional[str] = None, + reset_password_expiration_minutes: Optional[int] = None, + pkce_code_challenge: Optional[str] = None, + locale: Optional[str] = None, + ) -> ResetStartResponse: + """Initiates a password reset for the email address provided, when cross-org passwords are enabled. This will trigger an email to be sent to the address, containing a magic link that will allow them to set a new password and authenticate. + + This endpoint adapts to your Project's password strength configuration. + If you're using [zxcvbn](https://stytch.com/docs/guides/passwords/strength-policy), the default, your passwords are considered valid + if the strength score is >= 3. If you're using [LUDS](https://stytch.com/docs/guides/passwords/strength-policy), your passwords are + considered valid if they meet the requirements that you've set with Stytch. + You may update your password strength configuration in the [stytch dashboard](https://stytch.com/dashboard/password-strength-config). + + Fields: + - email_address: The email address of the Member to start the email reset process for. + - reset_password_redirect_url: The URL that the Member clicks from the reset password link. This URL should be an endpoint in the backend server that verifies the request by querying + Stytch's authenticate endpoint and finishes the reset password flow. If this value is not passed, the default `reset_password_redirect_url` that you set in your Dashboard is used. + If you have not set a default `reset_password_redirect_url`, an error is returned. + - discovery_redirect_url: The URL that the end user clicks from the discovery Magic Link. This URL should be an endpoint in the backend server that + verifies the request by querying Stytch's discovery authenticate endpoint and continues the flow. If this value is not passed, the default + discovery redirect URL that you set in your Dashboard is used. If you have not set a default discovery redirect URL, an error is returned. + - reset_password_template_id: Use a custom template for reset password emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for Magic Links - Reset Password. + - reset_password_expiration_minutes: Sets a time limit after which the email link to reset the member's password will no longer be valid. + - pkce_code_challenge: (no documentation yet) + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + } + if reset_password_redirect_url is not None: + data["reset_password_redirect_url"] = reset_password_redirect_url + if discovery_redirect_url is not None: + data["discovery_redirect_url"] = discovery_redirect_url + if reset_password_template_id is not None: + data["reset_password_template_id"] = reset_password_template_id + if reset_password_expiration_minutes is not None: + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) + if pkce_code_challenge is not None: + data["pkce_code_challenge"] = pkce_code_challenge + if locale is not None: + data["locale"] = locale + + url = self.api_base.url_for( + "/v1/b2b/passwords/discovery/email/reset/start", data + ) + res = self.sync_client.post(url, data, headers) + return ResetStartResponse.from_json(res.response.status_code, res.json) + + async def reset_start_async( + self, + email_address: str, + reset_password_redirect_url: Optional[str] = None, + discovery_redirect_url: Optional[str] = None, + reset_password_template_id: Optional[str] = None, + reset_password_expiration_minutes: Optional[int] = None, + pkce_code_challenge: Optional[str] = None, + locale: Optional[str] = None, + ) -> ResetStartResponse: + """Initiates a password reset for the email address provided, when cross-org passwords are enabled. This will trigger an email to be sent to the address, containing a magic link that will allow them to set a new password and authenticate. + + This endpoint adapts to your Project's password strength configuration. + If you're using [zxcvbn](https://stytch.com/docs/guides/passwords/strength-policy), the default, your passwords are considered valid + if the strength score is >= 3. If you're using [LUDS](https://stytch.com/docs/guides/passwords/strength-policy), your passwords are + considered valid if they meet the requirements that you've set with Stytch. + You may update your password strength configuration in the [stytch dashboard](https://stytch.com/dashboard/password-strength-config). + + Fields: + - email_address: The email address of the Member to start the email reset process for. + - reset_password_redirect_url: The URL that the Member clicks from the reset password link. This URL should be an endpoint in the backend server that verifies the request by querying + Stytch's authenticate endpoint and finishes the reset password flow. If this value is not passed, the default `reset_password_redirect_url` that you set in your Dashboard is used. + If you have not set a default `reset_password_redirect_url`, an error is returned. + - discovery_redirect_url: The URL that the end user clicks from the discovery Magic Link. This URL should be an endpoint in the backend server that + verifies the request by querying Stytch's discovery authenticate endpoint and continues the flow. If this value is not passed, the default + discovery redirect URL that you set in your Dashboard is used. If you have not set a default discovery redirect URL, an error is returned. + - reset_password_template_id: Use a custom template for reset password emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for Magic Links - Reset Password. + - reset_password_expiration_minutes: Sets a time limit after which the email link to reset the member's password will no longer be valid. + - pkce_code_challenge: (no documentation yet) + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + } + if reset_password_redirect_url is not None: + data["reset_password_redirect_url"] = reset_password_redirect_url + if discovery_redirect_url is not None: + data["discovery_redirect_url"] = discovery_redirect_url + if reset_password_template_id is not None: + data["reset_password_template_id"] = reset_password_template_id + if reset_password_expiration_minutes is not None: + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) + if pkce_code_challenge is not None: + data["pkce_code_challenge"] = pkce_code_challenge + if locale is not None: + data["locale"] = locale + + url = self.api_base.url_for( + "/v1/b2b/passwords/discovery/email/reset/start", data + ) + res = await self.async_client.post(url, data, headers) + return ResetStartResponse.from_json(res.response.status, res.json) + + def reset( + self, + password_reset_token: str, + password: str, + pkce_code_verifier: Optional[str] = None, + ) -> ResetResponse: + """Reset the password associated with an email and start an intermediate session. This endpoint checks that the password reset token is valid, hasn’t expired, or already been used. + + The provided password needs to meet the project's password strength requirements, which can be checked in advance with the password strength endpoint. If the token and password are accepted, the password is securely stored for future authentication and the user is authenticated. + + Resetting a password will start an intermediate session and return a list of discovered organizations the session can be exchanged into. + + Fields: + - password_reset_token: The password reset token to authenticate. + - password: The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc. + - pkce_code_verifier: (no documentation yet) + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "password_reset_token": password_reset_token, + "password": password, + } + if pkce_code_verifier is not None: + data["pkce_code_verifier"] = pkce_code_verifier + + url = self.api_base.url_for("/v1/b2b/passwords/discovery/email/reset", data) + res = self.sync_client.post(url, data, headers) + return ResetResponse.from_json(res.response.status_code, res.json) + + async def reset_async( + self, + password_reset_token: str, + password: str, + pkce_code_verifier: Optional[str] = None, + ) -> ResetResponse: + """Reset the password associated with an email and start an intermediate session. This endpoint checks that the password reset token is valid, hasn’t expired, or already been used. + + The provided password needs to meet the project's password strength requirements, which can be checked in advance with the password strength endpoint. If the token and password are accepted, the password is securely stored for future authentication and the user is authenticated. + + Resetting a password will start an intermediate session and return a list of discovered organizations the session can be exchanged into. + + Fields: + - password_reset_token: The password reset token to authenticate. + - password: The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc. + - pkce_code_verifier: (no documentation yet) + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "password_reset_token": password_reset_token, + "password": password, + } + if pkce_code_verifier is not None: + data["pkce_code_verifier"] = pkce_code_verifier + + url = self.api_base.url_for("/v1/b2b/passwords/discovery/email/reset", data) + res = await self.async_client.post(url, data, headers) + return ResetResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/passwords_email.py b/stytch/b2b/api/passwords_email.py index e529ba5..e907cbb 100644 --- a/stytch/b2b/api/passwords_email.py +++ b/stytch/b2b/api/passwords_email.py @@ -9,7 +9,8 @@ from typing import Any, Dict, Optional, Union from stytch.b2b.models.passwords_email import ( - DeleteResponse, + RequireResetRequestOptions, + RequireResetResponse, ResetRequestLocale, ResetResponse, ResetStartRequestLocale, @@ -317,13 +318,23 @@ async def reset_async( res = await self.async_client.post(url, data, headers) return ResetResponse.from_json(res.response.status, res.json) - def delete( + def require_reset( self, email_address: str, organization_id: Optional[str] = None, member_id: Optional[str] = None, - ) -> DeleteResponse: + method_options: Optional[RequireResetRequestOptions] = None, + ) -> RequireResetResponse: + """Require a password be reset by the associated email address. This endpoint is only functional for cross-org password use cases. + + Fields: + - email_address: The email address of the Member to start the email reset process for. + - 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. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) data: Dict[str, Any] = { "email_address": email_address, } @@ -332,17 +343,27 @@ def delete( if member_id is not None: data["member_id"] = member_id - url = self.api_base.url_for("/v1/b2b/passwords/email/delete", data) + url = self.api_base.url_for("/v1/b2b/passwords/email/require_reset", data) res = self.sync_client.post(url, data, headers) - return DeleteResponse.from_json(res.response.status_code, res.json) + return RequireResetResponse.from_json(res.response.status_code, res.json) - async def delete_async( + async def require_reset_async( self, email_address: str, organization_id: Optional[str] = None, member_id: Optional[str] = None, - ) -> DeleteResponse: + method_options: Optional[RequireResetRequestOptions] = None, + ) -> RequireResetResponse: + """Require a password be reset by the associated email address. This endpoint is only functional for cross-org password use cases. + + Fields: + - email_address: The email address of the Member to start the email reset process for. + - 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. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) data: Dict[str, Any] = { "email_address": email_address, } @@ -351,6 +372,6 @@ async def delete_async( if member_id is not None: data["member_id"] = member_id - url = self.api_base.url_for("/v1/b2b/passwords/email/delete", data) + url = self.api_base.url_for("/v1/b2b/passwords/email/require_reset", data) res = await self.async_client.post(url, data, headers) - return DeleteResponse.from_json(res.response.status, res.json) + return RequireResetResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/sso_external.py b/stytch/b2b/api/sso_external.py index cfad035..06b8609 100644 --- a/stytch/b2b/api/sso_external.py +++ b/stytch/b2b/api/sso_external.py @@ -149,12 +149,11 @@ def update_connection( - 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. - connection_id: Globally unique UUID that identifies a specific External SSO Connection. - display_name: A human-readable display name for the connection. - - external_connection_implicit_role_assignments: All Members who log in with this External connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection. + - external_connection_implicit_role_assignments: All Members who log in with this External connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection. - external_group_implicit_role_assignments: Defines the names of the groups that grant specific role assignments. For each group-Role pair, if a Member logs in with this external connection and belongs to the specified group, they will be granted the associated Role. See the - [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - Before adding any group implicit role assignments to an external connection, you must add a "groups" key to the underlying SAML connection's + [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. Before adding any group implicit role assignments to an external connection, you must add a "groups" key to the underlying SAML connection's `attribute_mapping`. Make sure that the SAML connection IdP is configured to correctly send the group information. Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection. """ # noqa @@ -203,12 +202,11 @@ async def update_connection_async( - 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. - connection_id: Globally unique UUID that identifies a specific External SSO Connection. - display_name: A human-readable display name for the connection. - - external_connection_implicit_role_assignments: All Members who log in with this External connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection. + - external_connection_implicit_role_assignments: All Members who log in with this External connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection. - external_group_implicit_role_assignments: Defines the names of the groups that grant specific role assignments. For each group-Role pair, if a Member logs in with this external connection and belongs to the specified group, they will be granted the associated Role. See the - [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - Before adding any group implicit role assignments to an external connection, you must add a "groups" key to the underlying SAML connection's + [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. Before adding any group implicit role assignments to an external connection, you must add a "groups" key to the underlying SAML connection's `attribute_mapping`. Make sure that the SAML connection IdP is configured to correctly send the group information. Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection. """ # noqa diff --git a/stytch/b2b/api/sso_saml.py b/stytch/b2b/api/sso_saml.py index b4d5510..6e244c7 100644 --- a/stytch/b2b/api/sso_saml.py +++ b/stytch/b2b/api/sso_saml.py @@ -137,8 +137,7 @@ def update_connection( - saml_group_implicit_role_assignments: Defines the names of the SAML groups that grant specific role assignments. For each group-Role pair, if a Member logs in with this SAML connection and belongs to the specified SAML group, they will be granted the associated Role. See the - [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - Before adding any group implicit role assignments, you must add a "groups" key to your SAML connection's + [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. Before adding any group implicit role assignments, you must add a "groups" key to your SAML connection's `attribute_mapping`. Make sure that your IdP is configured to correctly send the group information. - alternative_audience_uri: An alternative URL to use for the Audience Restriction. This value can be used when you wish to migrate an existing SAML integration to Stytch with zero downtime. Read our [SSO migration guide](https://stytch.com/docs/b2b/guides/migrations/additional-migration-considerations) for more info. - identity_provider: The identity provider of this connection. For OIDC, the accepted values are `generic`, `okta`, and `microsoft-entra`. For SAML, the accepted values are `generic`, `okta`, `microsoft-entra`, and `google-workspace`. @@ -220,8 +219,7 @@ async def update_connection_async( - saml_group_implicit_role_assignments: Defines the names of the SAML groups that grant specific role assignments. For each group-Role pair, if a Member logs in with this SAML connection and belongs to the specified SAML group, they will be granted the associated Role. See the - [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - Before adding any group implicit role assignments, you must add a "groups" key to your SAML connection's + [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. Before adding any group implicit role assignments, you must add a "groups" key to your SAML connection's `attribute_mapping`. Make sure that your IdP is configured to correctly send the group information. - alternative_audience_uri: An alternative URL to use for the Audience Restriction. This value can be used when you wish to migrate an existing SAML integration to Stytch with zero downtime. Read our [SSO migration guide](https://stytch.com/docs/b2b/guides/migrations/additional-migration-considerations) for more info. - identity_provider: The identity provider of this connection. For OIDC, the accepted values are `generic`, `okta`, and `microsoft-entra`. For SAML, the accepted values are `generic`, `okta`, `microsoft-entra`, and `google-workspace`. diff --git a/stytch/b2b/models/oauth_discovery.py b/stytch/b2b/models/oauth_discovery.py index 7f3b945..f7a75b2 100644 --- a/stytch/b2b/models/oauth_discovery.py +++ b/stytch/b2b/models/oauth_discovery.py @@ -29,8 +29,9 @@ class AuthenticateResponse(ResponseBase): 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). - - provider_type: (no documentation yet) - - provider_tenant_id: (no documentation yet) + - provider_type: Denotes the OAuth identity provider that the user has authenticated with, e.g. Google, Microsoft, GitHub etc. + - provider_tenant_id: The tenant ID returned by the OAuth provider. This is typically used to identify an organization or group within the provider's domain. For example, in HubSpot this is a Hub ID, in Slack this is the Workspace ID, and in GitHub this is an organization ID. This field will only be populated if exactly one tenant ID is returned from a successful OAuth authentication and developers should prefer `provider_tenant_ids` over this since it accounts for the possibility of an OAuth provider yielding multiple tenant IDs. + - provider_tenant_ids: All tenant IDs returned by the OAuth provider. These is typically used to identify organizations or groups within the provider's domain. For example, in HubSpot this is a Hub ID, in Slack this is the Workspace ID, and in GitHub this is an organization ID. Some OAuth providers do not return tenant IDs, some providers are guaranteed to return one, and some may return multiple. This field will always be populated if at least one tenant ID was returned from the OAuth provider and developers should prefer this field over `provider_tenant_id`. """ # noqa intermediate_session_token: str @@ -38,3 +39,4 @@ class AuthenticateResponse(ResponseBase): discovered_organizations: List[DiscoveredOrganization] provider_type: str provider_tenant_id: str + provider_tenant_ids: List[str] diff --git a/stytch/b2b/models/organizations.py b/stytch/b2b/models/organizations.py index a5dd35a..11ac094 100644 --- a/stytch/b2b/models/organizations.py +++ b/stytch/b2b/models/organizations.py @@ -84,6 +84,40 @@ class EmailImplicitRoleAssignment(pydantic.BaseModel): role_id: str +class GithubProviderInfo(pydantic.BaseModel): + """ + Fields: + - provider_subject: The unique identifier for the User within a given OAuth provider. Also commonly called the `sub` or "Subject field" in OAuth protocols. + - provider_tenant_ids: All tenant IDs returned by the OAuth provider. These is typically used to identify organizations or groups within the provider's domain. For example, in HubSpot this is a Hub ID, in Slack this is the Workspace ID, and in GitHub this is an organization ID. Some OAuth providers do not return tenant IDs, some providers are guaranteed to return one, and some may return multiple. This field will always be populated if at least one tenant ID was returned from the OAuth provider and developers should prefer this field over `provider_tenant_id`. + - 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. + """ # noqa + + provider_subject: str + provider_tenant_ids: List[str] + access_token: str + scopes: List[str] + + +class HubspotProviderInfo(pydantic.BaseModel): + """ + Fields: + - provider_subject: The unique identifier for the User within a given OAuth provider. Also commonly called the `sub` or "Subject field" in OAuth protocols. + - provider_tenant_id: The tenant ID returned by the OAuth provider. This is typically used to identify an organization or group within the provider's domain. For example, in HubSpot this is a Hub ID, in Slack this is the Workspace ID, and in GitHub this is an organization ID. This field will only be populated if exactly one tenant ID is returned from a successful OAuth authentication and developers should prefer `provider_tenant_ids` over this since it accounts for the possibility of an OAuth provider yielding multiple tenant IDs. + - access_token: The `access_token` that you may use to access the User's data in the provider's API. + - access_token_expires_in: The number of seconds until the access token expires. + - 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 obtain a new `access_token` for the User within the provider's API. + """ # noqa + + provider_subject: str + provider_tenant_id: str + access_token: str + access_token_expires_in: int + scopes: List[str] + refresh_token: Optional[str] = None + + class MemberRoleSource(pydantic.BaseModel): """ Fields: @@ -185,6 +219,17 @@ class OAuthRegistration(pydantic.BaseModel): class OIDCProviderInfo(pydantic.BaseModel): + """ + Fields: + - provider_subject: The unique identifier for the User within a given OAuth provider. Also commonly called the `sub` or "Subject field" in OAuth protocols. + - 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. + - access_token: The `access_token` that you may use to access the User's data in the provider's API. + - access_token_expires_in: The number of seconds until the access token expires. + - 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. + - connection_id: Globally unique UUID that identifies a specific SSO `connection_id` for a Member. + - refresh_token: The `refresh_token` that you may use to obtain a new `access_token` for the User within the provider's API. + """ # noqa + provider_subject: str id_token: str access_token: str @@ -264,7 +309,7 @@ class Organization(pydantic.BaseModel): - updated_at: The timestamp of when the Organization was last updated. Values conform to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`. - sso_default_connection_id: The default connection used for SSO when there are multiple active connections. - scim_active_connection: An active [SCIM Connection references](https://stytch.com/docs/b2b/api/scim-connection-object). - - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack" and "hubspot". + - allowed_oauth_tenants: A map of allowed OAuth tenants. If this field is not passed in, the Organization will not allow JIT provisioning by OAuth Tenant. Allowed keys are "slack", "hubspot", and "github". """ # noqa organization_id: str @@ -427,6 +472,25 @@ class SearchQuery(pydantic.BaseModel): operands: List[Dict[str, Any]] +class SlackProviderInfo(pydantic.BaseModel): + """ + Fields: + - provider_subject: The unique identifier for the User within a given OAuth provider. Also commonly called the `sub` or "Subject field" in OAuth protocols. + - provider_tenant_id: The tenant ID returned by the OAuth provider. This is typically used to identify an organization or group within the provider's domain. For example, in HubSpot this is a Hub ID, in Slack this is the Workspace ID, and in GitHub this is an organization ID. This field will only be populated if exactly one tenant ID is returned from a successful OAuth authentication and developers should prefer `provider_tenant_ids` over this since it accounts for the possibility of an OAuth provider yielding multiple tenant IDs. + - 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. + - bot_access_token: The `access_token` that you may use to access data as a bot application in Slack. Use in conjunction with `bot_scopes`. + - bot_scopes: The scopes that the bot application has access to in Slack. + """ # noqa + + provider_subject: str + provider_tenant_id: str + access_token: str + scopes: List[str] + bot_access_token: str + bot_scopes: List[str] + + class UpdateRequestOptions(pydantic.BaseModel): """ Fields: diff --git a/stytch/b2b/models/organizations_members.py b/stytch/b2b/models/organizations_members.py index a8ed24a..143a8fe 100644 --- a/stytch/b2b/models/organizations_members.py +++ b/stytch/b2b/models/organizations_members.py @@ -239,6 +239,11 @@ class GetResponse(ResponseBase): class OIDCProvidersResponse(ResponseBase): + """Response type for `Members.oidc_providers`. + Fields: + - registrations: A list of tokens the member is registered with. + """ # noqa + registrations: List[OIDCProviderInfo] diff --git a/stytch/b2b/models/organizations_members_oauth_providers.py b/stytch/b2b/models/organizations_members_oauth_providers.py index 499622b..efd7393 100644 --- a/stytch/b2b/models/organizations_members_oauth_providers.py +++ b/stytch/b2b/models/organizations_members_oauth_providers.py @@ -8,9 +8,25 @@ from typing import List, Optional +from stytch.b2b.models.organizations import ( + GithubProviderInfo, + HubspotProviderInfo, + SlackProviderInfo, +) from stytch.core.response_base import ResponseBase +class GithubResponse(ResponseBase): + """Response type for `OAuthProviders.github`. + Fields: + - provider_type: Denotes the OAuth identity provider that the user has authenticated with, e.g. Google, Microsoft, GitHub etc. + - registrations: A list of tokens the member is registered with. + """ # noqa + + provider_type: str + registrations: List[GithubProviderInfo] + + class GoogleResponse(ResponseBase): """Response type for `OAuthProviders.google`. Fields: @@ -32,6 +48,17 @@ class GoogleResponse(ResponseBase): refresh_token: Optional[str] = None +class HubspotResponse(ResponseBase): + """Response type for `OAuthProviders.hubspot`. + Fields: + - provider_type: Denotes the OAuth identity provider that the user has authenticated with, e.g. Google, Microsoft, GitHub etc. + - registrations: A list of tokens the member is registered with. + """ # noqa + + provider_type: str + registrations: List[HubspotProviderInfo] + + class MicrosoftResponse(ResponseBase): """Response type for `OAuthProviders.microsoft`. Fields: @@ -51,3 +78,14 @@ class MicrosoftResponse(ResponseBase): id_token: str scopes: List[str] refresh_token: Optional[str] = None + + +class SlackResponse(ResponseBase): + """Response type for `OAuthProviders.slack`. + Fields: + - provider_type: Denotes the OAuth identity provider that the user has authenticated with, e.g. Google, Microsoft, GitHub etc. + - registrations: A list of tokens the member is registered with. + """ # noqa + + provider_type: str + registrations: List[SlackProviderInfo] diff --git a/stytch/b2b/models/passwords_discovery.py b/stytch/b2b/models/passwords_discovery.py new file mode 100644 index 0000000..a539c95 --- /dev/null +++ b/stytch/b2b/models/passwords_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: + - email_address: The email address. + - intermediate_session_token: The returned Intermediate Session Token contains a password factor associated with the Member. If this value is non-empty, the member must complete an MFA step to finish logging in to the Organization. The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete an MFA flow and log in to the Organization. Password factors are not transferable between Organizations, so the intermediate session token is not valid for use with discovery endpoints. + - 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 + + email_address: str + intermediate_session_token: str + discovered_organizations: List[DiscoveredOrganization] diff --git a/stytch/b2b/models/passwords_discovery_email.py b/stytch/b2b/models/passwords_discovery_email.py new file mode 100644 index 0000000..f28a292 --- /dev/null +++ b/stytch/b2b/models/passwords_discovery_email.py @@ -0,0 +1,31 @@ +# !!! +# 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 ResetResponse(ResponseBase): + """Response type for `Email.reset`. + Fields: + - intermediate_session_token: The returned Intermediate Session Token contains a password factor associated with the Member. If this value is non-empty, the member must complete an MFA step to finish logging in to the Organization. The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete an MFA flow and log in to the Organization. Password factors are not transferable between Organizations, so the intermediate session token is not valid for use with discovery endpoints. + - email_address: (no documentation yet) + - discovered_organizations: (no documentation yet) + """ # noqa + + intermediate_session_token: str + email_address: str + discovered_organizations: List[DiscoveredOrganization] + + +class ResetStartResponse(ResponseBase): + """Response type for `Email.reset_start`. + Fields: + """ # noqa diff --git a/stytch/b2b/models/passwords_email.py b/stytch/b2b/models/passwords_email.py index 5b611b9..9e61f0e 100644 --- a/stytch/b2b/models/passwords_email.py +++ b/stytch/b2b/models/passwords_email.py @@ -7,12 +7,15 @@ from __future__ import annotations import enum -from typing import Optional +from typing import Dict, Optional + +import pydantic from stytch.b2b.models.mfa import MfaRequired from stytch.b2b.models.organizations import Member, Organization from stytch.b2b.models.sessions import MemberSession from stytch.core.response_base import ResponseBase +from stytch.shared.method_options import Authorization class ResetRequestLocale(str, enum.Enum): @@ -27,7 +30,30 @@ class ResetStartRequestLocale(str, enum.Enum): PTBR = "pt-br" -class DeleteResponse(ResponseBase): +class RequireResetRequestOptions(pydantic.BaseModel): + """ + Fields: + - authorization: Optional authorization object. + Pass in an active Stytch Member session token or session JWT and the request + will be run using that member's permissions. + """ # noqa + + authorization: Optional[Authorization] = None + + def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: + if self.authorization is not None: + headers = self.authorization.add_headers(headers) + return headers + + +class RequireResetResponse(ResponseBase): + """Response type for `Email.require_reset`. + Fields: + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - member_id: Globally unique UUID that identifies a specific Member. + """ # noqa + member: Member organization: Organization member_id: Optional[str] = None diff --git a/stytch/b2b/models/sso_oidc.py b/stytch/b2b/models/sso_oidc.py index 27f7016..1b7b2aa 100644 --- a/stytch/b2b/models/sso_oidc.py +++ b/stytch/b2b/models/sso_oidc.py @@ -17,17 +17,39 @@ class CreateConnectionRequestIdentityProvider(str, enum.Enum): + CLASSLINK = "classlink" + CYBERARK = "cyberark" + DUO = "duo" GENERIC = "generic" - OKTA = "okta" - MICROSOFTENTRA = "microsoft-entra" GOOGLEWORKSPACE = "google-workspace" + JUMPCLOUD = "jumpcloud" + KEYCLOAK = "keycloak" + MINIORANGE = "miniorange" + MICROSOFTENTRA = "microsoft-entra" + OKTA = "okta" + ONELOGIN = "onelogin" + PINGFEDERATE = "pingfederate" + RIPPLING = "rippling" + SALESFORCE = "salesforce" + SHIBBOLETH = "shibboleth" class UpdateConnectionRequestIdentityProvider(str, enum.Enum): + CLASSLINK = "classlink" + CYBERARK = "cyberark" + DUO = "duo" GENERIC = "generic" - OKTA = "okta" - MICROSOFTENTRA = "microsoft-entra" GOOGLEWORKSPACE = "google-workspace" + JUMPCLOUD = "jumpcloud" + KEYCLOAK = "keycloak" + MINIORANGE = "miniorange" + MICROSOFTENTRA = "microsoft-entra" + OKTA = "okta" + ONELOGIN = "onelogin" + PINGFEDERATE = "pingfederate" + RIPPLING = "rippling" + SALESFORCE = "salesforce" + SHIBBOLETH = "shibboleth" class CreateConnectionRequestOptions(pydantic.BaseModel): diff --git a/stytch/b2b/models/sso_saml.py b/stytch/b2b/models/sso_saml.py index 474c9a7..3d990c3 100644 --- a/stytch/b2b/models/sso_saml.py +++ b/stytch/b2b/models/sso_saml.py @@ -17,17 +17,39 @@ class CreateConnectionRequestIdentityProvider(str, enum.Enum): + CLASSLINK = "classlink" + CYBERARK = "cyberark" + DUO = "duo" GENERIC = "generic" - OKTA = "okta" - MICROSOFTENTRA = "microsoft-entra" GOOGLEWORKSPACE = "google-workspace" + JUMPCLOUD = "jumpcloud" + KEYCLOAK = "keycloak" + MINIORANGE = "miniorange" + MICROSOFTENTRA = "microsoft-entra" + OKTA = "okta" + ONELOGIN = "onelogin" + PINGFEDERATE = "pingfederate" + RIPPLING = "rippling" + SALESFORCE = "salesforce" + SHIBBOLETH = "shibboleth" class UpdateConnectionRequestIdentityProvider(str, enum.Enum): + CLASSLINK = "classlink" + CYBERARK = "cyberark" + DUO = "duo" GENERIC = "generic" - OKTA = "okta" - MICROSOFTENTRA = "microsoft-entra" GOOGLEWORKSPACE = "google-workspace" + JUMPCLOUD = "jumpcloud" + KEYCLOAK = "keycloak" + MINIORANGE = "miniorange" + MICROSOFTENTRA = "microsoft-entra" + OKTA = "okta" + ONELOGIN = "onelogin" + PINGFEDERATE = "pingfederate" + RIPPLING = "rippling" + SALESFORCE = "salesforce" + SHIBBOLETH = "shibboleth" class CreateConnectionRequestOptions(pydantic.BaseModel): diff --git a/stytch/consumer/api/webauthn.py b/stytch/consumer/api/webauthn.py index 15db1ff..304a697 100644 --- a/stytch/consumer/api/webauthn.py +++ b/stytch/consumer/api/webauthn.py @@ -34,6 +34,9 @@ def register_start( user_agent: Optional[str] = None, authenticator_type: Optional[str] = None, return_passkey_credential_options: Optional[bool] = None, + override_id: Optional[str] = None, + override_name: Optional[str] = None, + override_display_name: Optional[str] = None, ) -> RegisterStartResponse: """Initiate the process of creating a new Passkey or WebAuthn registration. @@ -50,6 +53,9 @@ def register_start( - authenticator_type: The requested authenticator type of the Passkey or WebAuthn device. The two valid values are platform and cross-platform. If no value passed, we assume both values are allowed. - return_passkey_credential_options: If true, the `public_key_credential_creation_options` returned will be optimized for Passkeys with `residentKey` set to `"required"` and `userVerification` set to `"preferred"`. + - override_id: (no documentation yet) + - override_name: (no documentation yet) + - override_display_name: (no documentation yet) """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -64,6 +70,12 @@ def register_start( data["return_passkey_credential_options"] = ( return_passkey_credential_options ) + if override_id is not None: + data["override_id"] = override_id + if override_name is not None: + data["override_name"] = override_name + if override_display_name is not None: + data["override_display_name"] = override_display_name url = self.api_base.url_for("/v1/webauthn/register/start", data) res = self.sync_client.post(url, data, headers) @@ -76,6 +88,9 @@ async def register_start_async( user_agent: Optional[str] = None, authenticator_type: Optional[str] = None, return_passkey_credential_options: Optional[bool] = None, + override_id: Optional[str] = None, + override_name: Optional[str] = None, + override_display_name: Optional[str] = None, ) -> RegisterStartResponse: """Initiate the process of creating a new Passkey or WebAuthn registration. @@ -92,6 +107,9 @@ async def register_start_async( - authenticator_type: The requested authenticator type of the Passkey or WebAuthn device. The two valid values are platform and cross-platform. If no value passed, we assume both values are allowed. - return_passkey_credential_options: If true, the `public_key_credential_creation_options` returned will be optimized for Passkeys with `residentKey` set to `"required"` and `userVerification` set to `"preferred"`. + - override_id: (no documentation yet) + - override_name: (no documentation yet) + - override_display_name: (no documentation yet) """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -106,6 +124,12 @@ async def register_start_async( data["return_passkey_credential_options"] = ( return_passkey_credential_options ) + if override_id is not None: + data["override_id"] = override_id + if override_name is not None: + data["override_name"] = override_name + if override_display_name is not None: + data["override_display_name"] = override_display_name url = self.api_base.url_for("/v1/webauthn/register/start", data) res = await self.async_client.post(url, data, headers) diff --git a/stytch/version.py b/stytch/version.py index 073f0d2..6f3778c 100644 --- a/stytch/version.py +++ b/stytch/version.py @@ -1 +1 @@ -__version__ = "11.7.0" +__version__ = "11.8.0"