# !!!
# 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, Union

import jwt

from stytch.b2b.models.sessions import (
    AttestResponse,
    AuthenticateJWTLocalResponse,
    AuthenticateResponse,
    AuthorizationCheck,
    ExchangeAccessTokenResponse,
    ExchangeRequestLocale,
    ExchangeResponse,
    GetJWKSResponse,
    GetResponse,
    LocalJWTResponse,
    MemberSession,
    MigrateResponse,
    RevokeRequestOptions,
    RevokeResponse,
)
from stytch.core.api_base import ApiBase
from stytch.core.http.client import AsyncClient, SyncClient
from stytch.shared import jwt_helpers, rbac_local
from stytch.shared.policy_cache import PolicyCache


class Sessions:
    def __init__(
        self,
        api_base: ApiBase,
        sync_client: SyncClient,
        async_client: AsyncClient,
        jwks_client: jwt.PyJWKClient,
        project_id: str,
        policy_cache: PolicyCache,
    ) -> None:
        self.api_base = api_base
        self.sync_client = sync_client
        self.async_client = async_client
        self.policy_cache = policy_cache
        self.jwks_client = jwks_client
        self.project_id = project_id

    def get(
        self,
        organization_id: str,
        member_id: str,
    ) -> GetResponse:
        """Retrieves all active Sessions for a Member.

        Fields:
          - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug here as a convenience.
          - 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. You may use an external_id here if one is set for the member.
        """  # 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/sessions", data)
        res = self.sync_client.get(url, data, headers)
        return GetResponse.from_json(res.response.status_code, res.json)

    async def get_async(
        self,
        organization_id: str,
        member_id: str,
    ) -> GetResponse:
        """Retrieves all active Sessions for a Member.

        Fields:
          - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug here as a convenience.
          - 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. You may use an external_id here if one is set for the member.
        """  # 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/sessions", data)
        res = await self.async_client.get(url, data, headers)
        return GetResponse.from_json(res.response.status, res.json)

    def authenticate(
        self,
        session_token: Optional[str] = None,
        session_duration_minutes: Optional[int] = None,
        session_jwt: Optional[str] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        authorization_check: Optional[Union[AuthorizationCheck, Dict[str, Any]]] = None,
    ) -> AuthenticateResponse:
        """Authenticates a Session and updates its lifetime by the specified `session_duration_minutes`. If the `session_duration_minutes` is not specified, a Session will not be extended. This endpoint requires either a `session_jwt` or `session_token` be included in the request. It will return an error if both are present.

        You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/resources/using-jwts) guide for more information.

        If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
        In addition, the `organization_id` passed in the authorization check must match the Member's Organization.

        If the Member is not authorized to perform the specified action on the specified Resource, or if the
        `organization_id` does not match the Member's Organization, a 403 error will be thrown.
        Otherwise, the response will contain a list of Roles that satisfied the authorization check.

        Fields:
          - 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.
          - authorization_check: If an `authorization_check` object is passed in, this endpoint will also check if the Member is
          authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if
          their Member Session contains a Role, assigned
          [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
          In addition, the `organization_id` passed in the authorization check must match the Member's Organization.

          The Roles on the Member Session may differ from the Roles you see on the Member object - Roles that are implicitly
          assigned by SSO connection or SSO group will only be valid for a Member Session if there is at least one authentication
          factor on the Member Session from the specified SSO connection.

          If the Member is not authorized to perform the specified action on the specified Resource, or if the
          `organization_id` does not match the Member's Organization, a 403 error will be thrown.
          Otherwise, the response will contain a list of Roles that satisfied the authorization check.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {}
        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 authorization_check is not None:
            data["authorization_check"] = (
                authorization_check
                if isinstance(authorization_check, dict)
                else authorization_check.dict()
            )

        url = self.api_base.url_for("/v1/b2b/sessions/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,
        session_token: Optional[str] = None,
        session_duration_minutes: Optional[int] = None,
        session_jwt: Optional[str] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        authorization_check: Optional[AuthorizationCheck] = None,
    ) -> AuthenticateResponse:
        """Authenticates a Session and updates its lifetime by the specified `session_duration_minutes`. If the `session_duration_minutes` is not specified, a Session will not be extended. This endpoint requires either a `session_jwt` or `session_token` be included in the request. It will return an error if both are present.

        You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/resources/using-jwts) guide for more information.

        If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
        In addition, the `organization_id` passed in the authorization check must match the Member's Organization.

        If the Member is not authorized to perform the specified action on the specified Resource, or if the
        `organization_id` does not match the Member's Organization, a 403 error will be thrown.
        Otherwise, the response will contain a list of Roles that satisfied the authorization check.

        Fields:
          - 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.
          - authorization_check: If an `authorization_check` object is passed in, this endpoint will also check if the Member is
          authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if
          their Member Session contains a Role, assigned
          [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
          In addition, the `organization_id` passed in the authorization check must match the Member's Organization.

          The Roles on the Member Session may differ from the Roles you see on the Member object - Roles that are implicitly
          assigned by SSO connection or SSO group will only be valid for a Member Session if there is at least one authentication
          factor on the Member Session from the specified SSO connection.

          If the Member is not authorized to perform the specified action on the specified Resource, or if the
          `organization_id` does not match the Member's Organization, a 403 error will be thrown.
          Otherwise, the response will contain a list of Roles that satisfied the authorization check.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {}
        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 authorization_check is not None:
            data["authorization_check"] = (
                authorization_check
                if isinstance(authorization_check, dict)
                else authorization_check.dict()
            )

        url = self.api_base.url_for("/v1/b2b/sessions/authenticate", data)
        res = await self.async_client.post(url, data, headers)
        return AuthenticateResponse.from_json(res.response.status, res.json)

    def revoke(
        self,
        member_session_id: Optional[str] = None,
        session_token: Optional[str] = None,
        session_jwt: Optional[str] = None,
        member_id: Optional[str] = None,
        method_options: Optional[RevokeRequestOptions] = None,
    ) -> RevokeResponse:
        """Revoke a Session and immediately invalidate all its tokens. To revoke a specific Session, pass either the `member_session_id`, `session_token`, or `session_jwt`. To revoke all Sessions for a Member, pass the `member_id`.

        Fields:
          - member_session_id: Globally unique UUID that identifies a specific Session in the Stytch API. The `member_session_id` is critical to perform operations on an Session, so be sure to preserve this value.
          - session_token: A secret token for a given Stytch Session.
          - session_jwt: The JSON Web Token (JWT) for a given Stytch Session.
          - 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] = {}
        if member_session_id is not None:
            data["member_session_id"] = member_session_id
        if session_token is not None:
            data["session_token"] = session_token
        if session_jwt is not None:
            data["session_jwt"] = session_jwt
        if member_id is not None:
            data["member_id"] = member_id

        url = self.api_base.url_for("/v1/b2b/sessions/revoke", data)
        res = self.sync_client.post(url, data, headers)
        return RevokeResponse.from_json(res.response.status_code, res.json)

    async def revoke_async(
        self,
        member_session_id: Optional[str] = None,
        session_token: Optional[str] = None,
        session_jwt: Optional[str] = None,
        member_id: Optional[str] = None,
        method_options: Optional[RevokeRequestOptions] = None,
    ) -> RevokeResponse:
        """Revoke a Session and immediately invalidate all its tokens. To revoke a specific Session, pass either the `member_session_id`, `session_token`, or `session_jwt`. To revoke all Sessions for a Member, pass the `member_id`.

        Fields:
          - member_session_id: Globally unique UUID that identifies a specific Session in the Stytch API. The `member_session_id` is critical to perform operations on an Session, so be sure to preserve this value.
          - session_token: A secret token for a given Stytch Session.
          - session_jwt: The JSON Web Token (JWT) for a given Stytch Session.
          - 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] = {}
        if member_session_id is not None:
            data["member_session_id"] = member_session_id
        if session_token is not None:
            data["session_token"] = session_token
        if session_jwt is not None:
            data["session_jwt"] = session_jwt
        if member_id is not None:
            data["member_id"] = member_id

        url = self.api_base.url_for("/v1/b2b/sessions/revoke", data)
        res = await self.async_client.post(url, data, headers)
        return RevokeResponse.from_json(res.response.status, res.json)

    def exchange(
        self,
        organization_id: str,
        session_token: Optional[str] = None,
        session_jwt: Optional[str] = None,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        locale: Optional[Union[ExchangeRequestLocale, str]] = 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.

        To create a new member via email domain JIT Provisioning, use the [Exchange Intermediate Session](https://stytch.com/docs/b2b/api/exchange-intermediate-session) flow instead.

        If the user **has** already satisfied the authentication requirements of the Organization they are trying to switch into, this API will return `member_authenticated: true` and a `session_token` and `session_jwt`.

        If the user **has not** satisfied the primary or secondary authentication requirements of the Organization they are attempting to switch into, this API will return `member_authenticated: false` and an `intermediate_session_token`.

        If `primary_required` is set, prompt the user to fulfill the Organization's auth requirements using the options returned in `primary_required.allowed_auth_methods`.

        If `primary_required` is null and `mfa_required` is set, check `mfa_required.member_options` to determine if the Member has SMS OTP or TOTP set up for MFA and prompt accordingly. If the Member has SMS OTP, check `mfa_required.secondary_auth_initiated` to see if the OTP has already been sent.

        Include the `intermediate_session_token` returned above when calling the `authenticate()` method that the user needed to perform. Once the user has completed the authentication requirements they were missing, they will be granted a full `session_token` and `session_jwt` to indicate they have successfully logged into the Organization.

        The `intermediate_session_token` can also be used with the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to join a different Organization or create a new one.
        The `session_duration_minutes` and `session_custom_claims` parameters will be ignored.

        Fields:
          - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug here as a convenience.
          - session_token: The `session_token` belonging to the member that you wish to associate the email with.
          - session_jwt: The `session_jwt` belonging to the member that you wish to associate the email with.
          - 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_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.
          - locale: If the Member needs to complete an MFA step, and the Member has a phone number, this endpoint will pre-emptively send a one-time passcode (OTP) to the Member's phone number. The locale argument will be used to determine which language to use when sending the passcode.

        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] = {
            "organization_id": organization_id,
        }
        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 locale is not None:
            data["locale"] = locale

        url = self.api_base.url_for("/v1/b2b/sessions/exchange", data)
        res = self.sync_client.post(url, data, headers)
        return ExchangeResponse.from_json(res.response.status_code, res.json)

    async def exchange_async(
        self,
        organization_id: str,
        session_token: Optional[str] = None,
        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.

        To create a new member via email domain JIT Provisioning, use the [Exchange Intermediate Session](https://stytch.com/docs/b2b/api/exchange-intermediate-session) flow instead.

        If the user **has** already satisfied the authentication requirements of the Organization they are trying to switch into, this API will return `member_authenticated: true` and a `session_token` and `session_jwt`.

        If the user **has not** satisfied the primary or secondary authentication requirements of the Organization they are attempting to switch into, this API will return `member_authenticated: false` and an `intermediate_session_token`.

        If `primary_required` is set, prompt the user to fulfill the Organization's auth requirements using the options returned in `primary_required.allowed_auth_methods`.

        If `primary_required` is null and `mfa_required` is set, check `mfa_required.member_options` to determine if the Member has SMS OTP or TOTP set up for MFA and prompt accordingly. If the Member has SMS OTP, check `mfa_required.secondary_auth_initiated` to see if the OTP has already been sent.

        Include the `intermediate_session_token` returned above when calling the `authenticate()` method that the user needed to perform. Once the user has completed the authentication requirements they were missing, they will be granted a full `session_token` and `session_jwt` to indicate they have successfully logged into the Organization.

        The `intermediate_session_token` can also be used with the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to join a different Organization or create a new one.
        The `session_duration_minutes` and `session_custom_claims` parameters will be ignored.

        Fields:
          - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug here as a convenience.
          - session_token: The `session_token` belonging to the member that you wish to associate the email with.
          - session_jwt: The `session_jwt` belonging to the member that you wish to associate the email with.
          - 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_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.
          - locale: If the Member needs to complete an MFA step, and the Member has a phone number, this endpoint will pre-emptively send a one-time passcode (OTP) to the Member's phone number. The locale argument will be used to determine which language to use when sending the passcode.

        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] = {
            "organization_id": organization_id,
        }
        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 locale is not None:
            data["locale"] = locale

        url = self.api_base.url_for("/v1/b2b/sessions/exchange", data)
        res = await self.async_client.post(url, data, headers)
        return ExchangeResponse.from_json(res.response.status, res.json)

    def exchange_access_token(
        self,
        access_token: str,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
    ) -> ExchangeAccessTokenResponse:
        """Use this endpoint to exchange a Connected Apps Access Token back into a Member Session for the underlying Member.
        This session can be used with the Stytch SDKs and APIs.

        The Access Token must contain the `full_access` scope (only available to First Party clients) and must not be more than 5 minutes old. Access Tokens may only be exchanged a single time.

        The Member Session returned will be the same Member Session that was active in your application (the authorizing party) during the initial authorization flow.

        Because the Member previously completed MFA and satisfied all Organization authentication requirements at the time of the original Access Token issuance, this endpoint will never return an `intermediate_session_token` or require MFA.

        Fields:
          - access_token: The access token to exchange for a Stytch Session. Must be granted the `full_access` scope.
          - 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_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.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "access_token": access_token,
        }
        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

        url = self.api_base.url_for("/v1/b2b/sessions/exchange_access_token", data)
        res = self.sync_client.post(url, data, headers)
        return ExchangeAccessTokenResponse.from_json(res.response.status_code, res.json)

    async def exchange_access_token_async(
        self,
        access_token: str,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
    ) -> ExchangeAccessTokenResponse:
        """Use this endpoint to exchange a Connected Apps Access Token back into a Member Session for the underlying Member.
        This session can be used with the Stytch SDKs and APIs.

        The Access Token must contain the `full_access` scope (only available to First Party clients) and must not be more than 5 minutes old. Access Tokens may only be exchanged a single time.

        The Member Session returned will be the same Member Session that was active in your application (the authorizing party) during the initial authorization flow.

        Because the Member previously completed MFA and satisfied all Organization authentication requirements at the time of the original Access Token issuance, this endpoint will never return an `intermediate_session_token` or require MFA.

        Fields:
          - access_token: The access token to exchange for a Stytch Session. Must be granted the `full_access` scope.
          - 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_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.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "access_token": access_token,
        }
        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

        url = self.api_base.url_for("/v1/b2b/sessions/exchange_access_token", data)
        res = await self.async_client.post(url, data, headers)
        return ExchangeAccessTokenResponse.from_json(res.response.status, res.json)

    def attest(
        self,
        organization_id: str,
        profile_id: str,
        token: str,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        session_token: Optional[str] = None,
        session_jwt: Optional[str] = None,
    ) -> AttestResponse:
        """Exchange an auth token issued by a trusted identity provider for a Stytch session. You must first register a Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/dashboard/trusted-auth-tokens).  If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.

        Fields:
          - organization_id: The organization ID that the session should be authenticated in.
          - profile_id: The ID of the trusted auth token profile to use for attestation.
          - token: The trusted auth token to authenticate.
          - 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_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.
          - session_token: The `session_token` for the session that you wish to add the trusted auth token authentication factor to.
          - session_jwt: The `session_jwt` for the session that you wish to add the trusted auth token authentication factor to.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "organization_id": organization_id,
            "profile_id": profile_id,
            "token": token,
        }
        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 session_token is not None:
            data["session_token"] = session_token
        if session_jwt is not None:
            data["session_jwt"] = session_jwt

        url = self.api_base.url_for("/v1/b2b/sessions/attest", data)
        res = self.sync_client.post(url, data, headers)
        return AttestResponse.from_json(res.response.status_code, res.json)

    async def attest_async(
        self,
        organization_id: str,
        profile_id: str,
        token: str,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        session_token: Optional[str] = None,
        session_jwt: Optional[str] = None,
    ) -> AttestResponse:
        """Exchange an auth token issued by a trusted identity provider for a Stytch session. You must first register a Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/dashboard/trusted-auth-tokens).  If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.

        Fields:
          - organization_id: The organization ID that the session should be authenticated in.
          - profile_id: The ID of the trusted auth token profile to use for attestation.
          - token: The trusted auth token to authenticate.
          - 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_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.
          - session_token: The `session_token` for the session that you wish to add the trusted auth token authentication factor to.
          - session_jwt: The `session_jwt` for the session that you wish to add the trusted auth token authentication factor to.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "organization_id": organization_id,
            "profile_id": profile_id,
            "token": token,
        }
        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 session_token is not None:
            data["session_token"] = session_token
        if session_jwt is not None:
            data["session_jwt"] = session_jwt

        url = self.api_base.url_for("/v1/b2b/sessions/attest", data)
        res = await self.async_client.post(url, data, headers)
        return AttestResponse.from_json(res.response.status, res.json)

    def migrate(
        self,
        session_token: str,
        organization_id: str,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
    ) -> MigrateResponse:
        """Migrate a session from an external OIDC compliant endpoint.
        Stytch will call the external UserInfo endpoint defined in your Stytch Project settings in the [Dashboard](https://stytch.com/dashboard/migrations), and then perform a lookup using the `session_token`.
        If the response contains a valid email address, Stytch will attempt to match that email address with an existing Member in your Organization and create a Stytch Session.
        You will need to create the member before using this endpoint.

        Fields:
          - session_token: The authorization token Stytch will pass in to the external userinfo endpoint.
          - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug here as a convenience.
          - 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_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.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "session_token": session_token,
            "organization_id": organization_id,
        }
        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

        url = self.api_base.url_for("/v1/b2b/sessions/migrate", data)
        res = self.sync_client.post(url, data, headers)
        return MigrateResponse.from_json(res.response.status_code, res.json)

    async def migrate_async(
        self,
        session_token: str,
        organization_id: str,
        session_duration_minutes: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
    ) -> MigrateResponse:
        """Migrate a session from an external OIDC compliant endpoint.
        Stytch will call the external UserInfo endpoint defined in your Stytch Project settings in the [Dashboard](https://stytch.com/dashboard/migrations), and then perform a lookup using the `session_token`.
        If the response contains a valid email address, Stytch will attempt to match that email address with an existing Member in your Organization and create a Stytch Session.
        You will need to create the member before using this endpoint.

        Fields:
          - session_token: The authorization token Stytch will pass in to the external userinfo endpoint.
          - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug here as a convenience.
          - 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_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.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "session_token": session_token,
            "organization_id": organization_id,
        }
        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

        url = self.api_base.url_for("/v1/b2b/sessions/migrate", data)
        res = await self.async_client.post(url, data, headers)
        return MigrateResponse.from_json(res.response.status, res.json)

    def get_jwks(
        self,
        project_id: str,
    ) -> GetJWKSResponse:
        """Get the JSON Web Key Set (JWKS) for a project.

        Within the JWKS, the JSON Web Keys are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key, and both keys will be returned by this endpoint for a period of 1 month.

        JWTs have a set lifetime of 5 minutes, so there will be a 5 minute period where some JWTs will be signed by the old keys, and some JWTs will be signed by the new keys. The correct key to use for validation is determined by matching the `kid` value of the JWT and key.

        If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JSON Web Key (JWK) rotation will be handled for you.

        If you're using your own JWT validation library, many have built-in support for JWK rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWK to use for validation by inspecting the `kid` value.

        See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/resources/using-jwts) guide for more information.

        Fields:
          - project_id: The `project_id` to get the JWKS for.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "project_id": project_id,
        }

        url = self.api_base.url_for("/v1/b2b/sessions/jwks/{project_id}", data)
        res = self.sync_client.get(url, data, headers)
        return GetJWKSResponse.from_json(res.response.status_code, res.json)

    async def get_jwks_async(
        self,
        project_id: str,
    ) -> GetJWKSResponse:
        """Get the JSON Web Key Set (JWKS) for a project.

        Within the JWKS, the JSON Web Keys are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key, and both keys will be returned by this endpoint for a period of 1 month.

        JWTs have a set lifetime of 5 minutes, so there will be a 5 minute period where some JWTs will be signed by the old keys, and some JWTs will be signed by the new keys. The correct key to use for validation is determined by matching the `kid` value of the JWT and key.

        If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JSON Web Key (JWK) rotation will be handled for you.

        If you're using your own JWT validation library, many have built-in support for JWK rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWK to use for validation by inspecting the `kid` value.

        See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/resources/using-jwts) guide for more information.

        Fields:
          - project_id: The `project_id` to get the JWKS for.
        """  # noqa
        headers: Dict[str, str] = {}
        data: Dict[str, Any] = {
            "project_id": project_id,
        }

        url = self.api_base.url_for("/v1/b2b/sessions/jwks/{project_id}", data)
        res = await self.async_client.get(url, data, headers)
        return GetJWKSResponse.from_json(res.response.status, res.json)

    # MANUAL(authenticate_jwt)(SERVICE_METHOD)
    # ADDIMPORT: from typing import Any, Dict, Optional
    # ADDIMPORT: from stytch.shared import rbac_local
    # ADDIMPORT: from stytch.b2b.models.sessions import AuthenticateJWTLocalResponse
    def authenticate_jwt(
        self,
        session_jwt: str,
        max_token_age_seconds: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        authorization_check: Optional[AuthorizationCheck] = None,
    ) -> AuthenticateJWTLocalResponse:
        """Parse a JWT and verify the signature, preferring local verification
        over remote.

        If max_token_age_seconds is set, remote verification will be forced if the
        JWT was issued at (based on the "iat" claim) more than that many seconds ago.

        To force remote validation for all tokens, set max_token_age_seconds to
        zero or use the authenticate method instead.
        """
        # Return the local_result if available, otherwise call the Stytch API
        local_session = self.authenticate_jwt_local(
            session_jwt=session_jwt,
            max_token_age_seconds=max_token_age_seconds,
            authorization_check=authorization_check,
        )
        if local_session is not None:
            return AuthenticateJWTLocalResponse.from_json(
                status_code=200,
                json={
                    "member_session": local_session,
                    "session_jwt": session_jwt,
                    "status_code": 200,
                    "request_id": "",
                },
            )
        else:
            network_resp = self.authenticate(
                session_custom_claims=session_custom_claims,
                session_jwt=session_jwt,
                authorization_check=authorization_check,
            )
            return AuthenticateJWTLocalResponse.from_json(
                status_code=network_resp.status_code,
                json={
                    "member_session": network_resp.member_session,
                    "session_jwt": network_resp.session_jwt,
                    "status_code": network_resp.status_code,
                    "request_id": network_resp.request_id,
                },
            )

    async def authenticate_jwt_async(
        self,
        session_jwt: str,
        max_token_age_seconds: Optional[int] = None,
        session_custom_claims: Optional[Dict[str, Any]] = None,
        authorization_check: Optional[AuthorizationCheck] = None,
    ) -> AuthenticateJWTLocalResponse:
        """Parse a JWT and verify the signature, preferring local verification
        over remote.

        If max_token_age_seconds is set, remote verification will be forced if the
        JWT was issued at (based on the "iat" claim) more than that many seconds ago.

        To force remote validation for all tokens, set max_token_age_seconds to
        zero or use the authenticate method instead.
        """
        # Return the local_result if available, otherwise call the Stytch API
        local_session = await self.authenticate_jwt_local_async(
            session_jwt=session_jwt,
            max_token_age_seconds=max_token_age_seconds,
            authorization_check=authorization_check,
        )
        if local_session is not None:
            return AuthenticateJWTLocalResponse.from_json(
                status_code=200,
                json={
                    "member_session": local_session,
                    "session_jwt": session_jwt,
                    "status_code": 200,
                    "request_id": "",
                },
            )
        else:
            network_resp = await self.authenticate_async(
                session_custom_claims=session_custom_claims,
                session_jwt=session_jwt,
                authorization_check=authorization_check,
            )
            return AuthenticateJWTLocalResponse.from_json(
                status_code=network_resp.status_code,
                json={
                    "member_session": network_resp.member_session,
                    "session_jwt": network_resp.session_jwt,
                    "status_code": network_resp.status_code,
                    "request_id": network_resp.request_id,
                },
            )

    # ENDMANUAL(authenticate_jwt)

    # MANUAL(authenticate_jwt_local)(SERVICE_METHOD)
    # ADDIMPORT: from typing import Optional
    # ADDIMPORT: from stytch.b2b.models.sessions import MemberSession, LocalJWTResponse
    # ADDIMPORT: from stytch.shared import jwt_helpers
    # ADDIMPORT: from stytch.shared import rbac_local
    def _authenticate_jwt_local_common(
        self,
        session_jwt: str,
        max_token_age_seconds: Optional[int] = None,
        leeway: int = 0,
    ) -> Optional[LocalJWTResponse]:
        _session_claim = "https://stytch.com/session"
        _organization_claim = "https://stytch.com/organization"
        generic_claims = jwt_helpers.authenticate_jwt_local(
            project_id=self.project_id,
            jwks_client=self.jwks_client,
            jwt=session_jwt,
            max_token_age_seconds=max_token_age_seconds,
            leeway=leeway,
            base_url=self.api_base.base_url,
        )
        if generic_claims is None:
            return None

        claim = generic_claims.untyped_claims[_session_claim]
        custom_claims = {
            k: v
            for k, v in generic_claims.untyped_claims.items()
            if k not in [_session_claim, _organization_claim]
        }

        # For JWTs that include it, prefer the inner expires_at claim.
        expires_at = claim.get("expires_at", generic_claims.reserved_claims["exp"])

        # Claim related to unpacking organization-specific fields
        org_claim = generic_claims.untyped_claims[_organization_claim]

        # Claim related to RBAC roles
        roles_claim = claim.get("roles")
        if roles_claim is not None:
            if not isinstance(roles_claim, list) or not all(
                isinstance(x, str) for x in roles_claim
            ):
                raise ValueError("Invalid roles claim. Expected a list of strings.")

        return LocalJWTResponse(
            member_session=MemberSession(
                authentication_factors=claim["authentication_factors"],
                expires_at=expires_at,
                last_accessed_at=claim["last_accessed_at"],
                member_session_id=claim["id"],
                started_at=claim["started_at"],
                organization_id=org_claim["organization_id"],
                member_id=generic_claims.reserved_claims["sub"],
                custom_claims=custom_claims,
                roles=roles_claim or [],
                organization_slug=org_claim["slug"],
            ),
            roles_claim=roles_claim,
        )

    def authenticate_jwt_local(
        self,
        session_jwt: str,
        max_token_age_seconds: Optional[int] = None,
        leeway: int = 0,
        authorization_check: Optional[AuthorizationCheck] = None,
    ) -> Optional[MemberSession]:
        local_resp = self._authenticate_jwt_local_common(
            session_jwt=session_jwt,
            max_token_age_seconds=max_token_age_seconds,
            leeway=leeway,
        )
        if local_resp is None:
            return None

        if authorization_check is not None:
            if local_resp.roles_claim is None:
                raise ValueError("Invalid roles claim. Expected a list of strings.")

            rbac_local.perform_authorization_check(
                policy=self.policy_cache.get(),
                subject_roles=local_resp.roles_claim,
                subject_org_id=local_resp.member_session.organization_id,
                authorization_check=authorization_check,
            )

        # Auth check passes (or wasn't provided), we can return the session now
        return local_resp.member_session

    async def authenticate_jwt_local_async(
        self,
        session_jwt: str,
        max_token_age_seconds: Optional[int] = None,
        leeway: int = 0,
        authorization_check: Optional[AuthorizationCheck] = None,
    ) -> Optional[MemberSession]:
        local_resp = self._authenticate_jwt_local_common(
            session_jwt=session_jwt,
            max_token_age_seconds=max_token_age_seconds,
            leeway=leeway,
        )
        if local_resp is None:
            return None

        if authorization_check is not None:
            if local_resp.roles_claim is None:
                raise ValueError("Invalid roles claim. Expected a list of strings.")

            rbac_local.perform_authorization_check(
                policy=await self.policy_cache.get_async(),
                subject_roles=local_resp.roles_claim,
                subject_org_id=local_resp.member_session.organization_id,
                authorization_check=authorization_check,
            )

        # Auth check passes (or wasn't provided), we can return the session now
        return local_resp.member_session

    # ENDMANUAL(authenticate_jwt_local)
