All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.com.stytch.java.b2b.api.sessions.Sessions.kt Maven / Gradle / Ivy

There is a newer version: 6.7.0
Show newest version
package com.stytch.java.b2b.api.sessions

// !!!
// WARNING: This file is autogenerated
// Only modify code within MANUAL() sections
// or your changes may be overwritten later!
// !!!

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.stytch.java.b2b.models.sessions.AuthenticateRequest
import com.stytch.java.b2b.models.sessions.AuthenticateResponse
import com.stytch.java.b2b.models.sessions.AuthorizationCheck
import com.stytch.java.b2b.models.sessions.ExchangeRequest
import com.stytch.java.b2b.models.sessions.ExchangeResponse
import com.stytch.java.b2b.models.sessions.GetJWKSRequest
import com.stytch.java.b2b.models.sessions.GetJWKSResponse
import com.stytch.java.b2b.models.sessions.GetRequest
import com.stytch.java.b2b.models.sessions.GetResponse
import com.stytch.java.b2b.models.sessions.MemberSession
import com.stytch.java.b2b.models.sessions.MigrateRequest
import com.stytch.java.b2b.models.sessions.MigrateResponse
import com.stytch.java.b2b.models.sessions.RevokeRequest
import com.stytch.java.b2b.models.sessions.RevokeRequestOptions
import com.stytch.java.b2b.models.sessions.RevokeResponse
import com.stytch.java.common.InstantAdapter
import com.stytch.java.common.JWTException
import com.stytch.java.common.JwtOptions
import com.stytch.java.common.ParseJWTClaimsOptions
import com.stytch.java.common.PolicyCache
import com.stytch.java.common.StytchException
import com.stytch.java.common.StytchResult
import com.stytch.java.common.StytchSessionClaim
import com.stytch.java.common.parseJWTClaims
import com.stytch.java.http.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jose4j.jwk.HttpsJwks
import java.time.Instant
import java.util.concurrent.CompletableFuture

public interface Sessions {
    /**
     * Retrieves all active Sessions for a Member.
     */
    public suspend fun get(data: GetRequest): StytchResult

    /**
     * Retrieves all active Sessions for a Member.
     */
    public fun get(
        data: GetRequest,
        callback: (StytchResult) -> Unit,
    )

    /**
     * Retrieves all active Sessions for a Member.
     */
    public fun getCompletable(data: GetRequest): CompletableFuture>

    /**
     * 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. A 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.
     */
    public suspend fun authenticate(data: AuthenticateRequest): StytchResult

    /**
     * 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. A 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.
     */
    public fun authenticate(
        data: AuthenticateRequest,
        callback: (StytchResult) -> Unit,
    )

    /**
     * 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. A 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.
     */
    public fun authenticateCompletable(data: AuthenticateRequest): CompletableFuture>

    /**
     * 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`.
     */
    public suspend fun revoke(
        data: RevokeRequest,
        methodOptions: RevokeRequestOptions? = null,
    ): StytchResult

    /**
     * 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`.
     */
    public fun revoke(
        data: RevokeRequest,
        methodOptions: RevokeRequestOptions? = null,
        callback: (StytchResult) -> Unit,
    )

    /**
     * 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`.
     */
    public fun revokeCompletable(
        data: RevokeRequest,
        methodOptions: RevokeRequestOptions? = null,
    ): CompletableFuture>

    /**
     * Use this endpoint to exchange a's existing session for another session in a different. This can be used to accept an
     * invite, but not to create a new member via domain matching.
     *
     * To create a new member via domain matching, use the
     * [Exchange Intermediate Session](https://stytch.com/docs/b2b/api/exchange-intermediate-session) flow instead.
     *
     * Only Email Magic Link, OAuth, and SMS OTP factors can be transferred between sessions. Other authentication factors,
     * such as password factors, will not be transferred to the new session.
     * Any OAuth Tokens owned by the Member will not be transferred to the new Organization.
     * SMS OTP factors can be used to fulfill MFA requirements for the target Organization if both the original and target
     * Member have the same phone number and the phone number is verified for both Members.
     * HubSpot and Slack OAuth registrations will not be transferred between sessions. Instead, you will receive a
     * corresponding factor with type `"oauth_exchange_slack"` or `"oauth_exchange_hubspot"`
     *
     * If the Member is required to complete MFA to log in to the Organization, the returned value of `member_authenticated`
     * will be `false`, and an `intermediate_session_token` will be returned.
     * The `intermediate_session_token` can be passed into the
     * [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete the MFA step and
     * acquire a full member session.
     * 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.
     */
    public suspend fun exchange(data: ExchangeRequest): StytchResult

    /**
     * Use this endpoint to exchange a's existing session for another session in a different. This can be used to accept an
     * invite, but not to create a new member via domain matching.
     *
     * To create a new member via domain matching, use the
     * [Exchange Intermediate Session](https://stytch.com/docs/b2b/api/exchange-intermediate-session) flow instead.
     *
     * Only Email Magic Link, OAuth, and SMS OTP factors can be transferred between sessions. Other authentication factors,
     * such as password factors, will not be transferred to the new session.
     * Any OAuth Tokens owned by the Member will not be transferred to the new Organization.
     * SMS OTP factors can be used to fulfill MFA requirements for the target Organization if both the original and target
     * Member have the same phone number and the phone number is verified for both Members.
     * HubSpot and Slack OAuth registrations will not be transferred between sessions. Instead, you will receive a
     * corresponding factor with type `"oauth_exchange_slack"` or `"oauth_exchange_hubspot"`
     *
     * If the Member is required to complete MFA to log in to the Organization, the returned value of `member_authenticated`
     * will be `false`, and an `intermediate_session_token` will be returned.
     * The `intermediate_session_token` can be passed into the
     * [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete the MFA step and
     * acquire a full member session.
     * 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.
     */
    public fun exchange(
        data: ExchangeRequest,
        callback: (StytchResult) -> Unit,
    )

    /**
     * Use this endpoint to exchange a's existing session for another session in a different. This can be used to accept an
     * invite, but not to create a new member via domain matching.
     *
     * To create a new member via domain matching, use the
     * [Exchange Intermediate Session](https://stytch.com/docs/b2b/api/exchange-intermediate-session) flow instead.
     *
     * Only Email Magic Link, OAuth, and SMS OTP factors can be transferred between sessions. Other authentication factors,
     * such as password factors, will not be transferred to the new session.
     * Any OAuth Tokens owned by the Member will not be transferred to the new Organization.
     * SMS OTP factors can be used to fulfill MFA requirements for the target Organization if both the original and target
     * Member have the same phone number and the phone number is verified for both Members.
     * HubSpot and Slack OAuth registrations will not be transferred between sessions. Instead, you will receive a
     * corresponding factor with type `"oauth_exchange_slack"` or `"oauth_exchange_hubspot"`
     *
     * If the Member is required to complete MFA to log in to the Organization, the returned value of `member_authenticated`
     * will be `false`, and an `intermediate_session_token` will be returned.
     * The `intermediate_session_token` can be passed into the
     * [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete the MFA step and
     * acquire a full member session.
     * 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.
     */
    public fun exchangeCompletable(data: ExchangeRequest): CompletableFuture>

    /**
     * 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](/dashboard), 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 in your
     * and create a Stytch Session. You will need to create the member before using this endpoint.
     */
    public suspend fun migrate(data: MigrateRequest): StytchResult

    /**
     * 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](/dashboard), 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 in your
     * and create a Stytch Session. You will need to create the member before using this endpoint.
     */
    public fun migrate(
        data: MigrateRequest,
        callback: (StytchResult) -> Unit,
    )

    /**
     * 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](/dashboard), 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 in your
     * and create a Stytch Session. You will need to create the member before using this endpoint.
     */
    public fun migrateCompletable(data: MigrateRequest): CompletableFuture>

    /**
     * Get the JSON Web Key Set (JWKS) for a project.
     *
     * JWKS are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key set, and both key sets 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
     * JWKS, and some JWTs will be signed by the new JWKS. The correct JWKS to use for validation is determined by matching
     * the `kid` value of the JWT and JWKS.
     *
     * If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JWKS roll will be handled for you.
     *
     * If you're using your own JWT validation library, many have built-in support for JWKS rotation, and you'll just need to
     * supply this API endpoint. If not, your application should decide which JWKS 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.
     */
    public suspend fun getJWKS(data: GetJWKSRequest): StytchResult

    /**
     * Get the JSON Web Key Set (JWKS) for a project.
     *
     * JWKS are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key set, and both key sets 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
     * JWKS, and some JWTs will be signed by the new JWKS. The correct JWKS to use for validation is determined by matching
     * the `kid` value of the JWT and JWKS.
     *
     * If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JWKS roll will be handled for you.
     *
     * If you're using your own JWT validation library, many have built-in support for JWKS rotation, and you'll just need to
     * supply this API endpoint. If not, your application should decide which JWKS 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.
     */
    public fun getJWKS(
        data: GetJWKSRequest,
        callback: (StytchResult) -> Unit,
    )

    /**
     * Get the JSON Web Key Set (JWKS) for a project.
     *
     * JWKS are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key set, and both key sets 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
     * JWKS, and some JWTs will be signed by the new JWKS. The correct JWKS to use for validation is determined by matching
     * the `kid` value of the JWT and JWKS.
     *
     * If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JWKS roll will be handled for you.
     *
     * If you're using your own JWT validation library, many have built-in support for JWKS rotation, and you'll just need to
     * supply this API endpoint. If not, your application should decide which JWKS 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.
     */
    public fun getJWKSCompletable(data: GetJWKSRequest): CompletableFuture>

    // MANUAL(authenticateJWT_interface)(INTERFACE_METHOD)
    // ADDIMPORT: import com.stytch.java.b2b.models.sessions.AuthorizationCheck
    // ADDIMPORT: import com.stytch.java.b2b.models.sessions.MemberSession
    // ADDIMPORT: import com.stytch.java.common.JWTException
    // ADDIMPORT: import com.stytch.java.common.ParseJWTClaimsOptions
    // ADDIMPORT: import com.stytch.java.common.StytchSessionClaim
    // ADDIMPORT: import com.stytch.java.common.parseJWTClaims

    /** Parse a JWT and verify the signature, preferring local verification over remote.
     *
     * If maxTokenAgeSeconds 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 maxTokenAgeSeconds to zero or use the
     * authenticate method instead.
     */
    public suspend fun authenticateJwt(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck? = null,
    ): StytchResult

    /** Parse a JWT and verify the signature, preferring local verification over remote.
     *
     * If maxTokenAgeSeconds 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 maxTokenAgeSeconds to zero or use the
     * authenticate method instead.
     */
    public fun authenticateJwt(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck? = null,
        callback: (StytchResult) -> Unit,
    )

    /** Parse a JWT and verify the signature, preferring local verification over remote.
     *
     * If maxTokenAgeSeconds 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 maxTokenAgeSeconds to zero or use the
     * authenticate method instead.
     */
    public fun authenticateJwtCompletable(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck? = null,
    ): CompletableFuture>

    /** Parse a JWT and verify the signature locally (without calling /authenticate in the API).
     *
     * If maxTokenAgeSeconds is set, this will return an error if the JWT was issued (based on the "iat"
     * claim) more than maxTokenAge seconds ago.
     *
     * If maxTokenAgeSeconds is explicitly set to zero, all tokens will be considered too old,
     * even if they are otherwise valid.
     *
     * The value for leeway is the maximum allowable difference when comparing
     * timestamps. It defaults to zero.
     */
    public suspend fun authenticateJwtLocal(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck? = null,
        leeway: Int = 0,
    ): StytchResult

    /** Parse a JWT and verify the signature locally (without calling /authenticate in the API).
     *
     * If maxTokenAgeSeconds is set, this will return an error if the JWT was issued (based on the "iat"
     * claim) more than maxTokenAge seconds ago.
     *
     * If maxTokenAgeSeconds is explicitly set to zero, all tokens will be considered too old,
     * even if they are otherwise valid.
     *
     * The value for leeway is the maximum allowable difference when comparing
     * timestamps. It defaults to zero.
     */
    public fun authenticateJwtLocal(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck? = null,
        leeway: Int = 0,
        callback: (StytchResult) -> Unit,
    )

    /** Parse a JWT and verify the signature locally (without calling /authenticate in the API).
     *
     * If maxTokenAgeSeconds is set, this will return an error if the JWT was issued (based on the "iat"
     * claim) more than maxTokenAge seconds ago.
     *
     * If maxTokenAgeSeconds is explicitly set to zero, all tokens will be considered too old,
     * even if they are otherwise valid.
     *
     * The value for leeway is the maximum allowable difference when comparing
     * timestamps. It defaults to zero.
     */
    public fun authenticateJwtLocalCompletable(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck? = null,
        leeway: Int = 0,
    ): CompletableFuture>
    // ENDMANUAL(authenticateJWT_interface)
}

internal class SessionsImpl(
    private val httpClient: HttpClient,
    private val coroutineScope: CoroutineScope,
    private val jwksClient: HttpsJwks,
    private val jwtOptions: JwtOptions,
    private val policyCache: PolicyCache,
) : Sessions {
    private val moshi = Moshi.Builder().add(InstantAdapter()).build()

    override suspend fun get(data: GetRequest): StytchResult =
        withContext(Dispatchers.IO) {
            var headers = emptyMap()

            val asJson = moshi.adapter(GetRequest::class.java).toJson(data)
            val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
            val adapter: JsonAdapter> = moshi.adapter(type)
            val asMap = adapter.fromJson(asJson) ?: emptyMap()
            httpClient.get("/v1/b2b/sessions", asMap, headers)
        }

    override fun get(
        data: GetRequest,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(get(data))
        }
    }

    override fun getCompletable(data: GetRequest): CompletableFuture> =
        coroutineScope.async {
            get(data)
        }.asCompletableFuture()

    override suspend fun authenticate(data: AuthenticateRequest): StytchResult =
        withContext(Dispatchers.IO) {
            var headers = emptyMap()

            val asJson = moshi.adapter(AuthenticateRequest::class.java).toJson(data)
            httpClient.post("/v1/b2b/sessions/authenticate", asJson, headers)
        }

    override fun authenticate(
        data: AuthenticateRequest,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(authenticate(data))
        }
    }

    override fun authenticateCompletable(data: AuthenticateRequest): CompletableFuture> =
        coroutineScope.async {
            authenticate(data)
        }.asCompletableFuture()

    override suspend fun revoke(
        data: RevokeRequest,
        methodOptions: RevokeRequestOptions?,
    ): StytchResult =
        withContext(Dispatchers.IO) {
            var headers = emptyMap()
            methodOptions?.let {
                headers = methodOptions.addHeaders(headers)
            }

            val asJson = moshi.adapter(RevokeRequest::class.java).toJson(data)
            httpClient.post("/v1/b2b/sessions/revoke", asJson, headers)
        }

    override fun revoke(
        data: RevokeRequest,
        methodOptions: RevokeRequestOptions?,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(revoke(data, methodOptions))
        }
    }

    override fun revokeCompletable(
        data: RevokeRequest,
        methodOptions: RevokeRequestOptions?,
    ): CompletableFuture> =
        coroutineScope.async {
            revoke(data, methodOptions)
        }.asCompletableFuture()

    override suspend fun exchange(data: ExchangeRequest): StytchResult =
        withContext(Dispatchers.IO) {
            var headers = emptyMap()

            val asJson = moshi.adapter(ExchangeRequest::class.java).toJson(data)
            httpClient.post("/v1/b2b/sessions/exchange", asJson, headers)
        }

    override fun exchange(
        data: ExchangeRequest,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(exchange(data))
        }
    }

    override fun exchangeCompletable(data: ExchangeRequest): CompletableFuture> =
        coroutineScope.async {
            exchange(data)
        }.asCompletableFuture()

    override suspend fun migrate(data: MigrateRequest): StytchResult =
        withContext(Dispatchers.IO) {
            var headers = emptyMap()

            val asJson = moshi.adapter(MigrateRequest::class.java).toJson(data)
            httpClient.post("/v1/b2b/sessions/migrate", asJson, headers)
        }

    override fun migrate(
        data: MigrateRequest,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(migrate(data))
        }
    }

    override fun migrateCompletable(data: MigrateRequest): CompletableFuture> =
        coroutineScope.async {
            migrate(data)
        }.asCompletableFuture()

    override suspend fun getJWKS(data: GetJWKSRequest): StytchResult =
        withContext(Dispatchers.IO) {
            var headers = emptyMap()

            val asJson = moshi.adapter(GetJWKSRequest::class.java).toJson(data)
            val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
            val adapter: JsonAdapter> = moshi.adapter(type)
            val asMap = adapter.fromJson(asJson) ?: emptyMap()
            httpClient.get("/v1/b2b/sessions/jwks/${data.projectId}", asMap, headers)
        }

    override fun getJWKS(
        data: GetJWKSRequest,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(getJWKS(data))
        }
    }

    override fun getJWKSCompletable(data: GetJWKSRequest): CompletableFuture> =
        coroutineScope.async {
            getJWKS(data)
        }.asCompletableFuture()

    // MANUAL(authenticateJWT_impl)(SERVICE_METHOD)
    override suspend fun authenticateJwt(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck?,
    ): StytchResult =
        withContext(Dispatchers.IO) {
            when (
                val localResult =
                    authenticateJwtLocal(jwt = jwt, maxTokenAgeSeconds = maxTokenAgeSeconds, authorizationCheck = authorizationCheck)
            ) {
                is StytchResult.Success -> StytchResult.Success(localResult.value)
                else ->
                    when (val netResult = authenticate(AuthenticateRequest(sessionJwt = jwt, authorizationCheck = authorizationCheck))) {
                        is StytchResult.Success -> StytchResult.Success(netResult.value.memberSession)
                        else -> StytchResult.Success(null)
                    }
            }
        }

    override fun authenticateJwt(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck?,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(authenticateJwt(jwt, maxTokenAgeSeconds, authorizationCheck))
        }
    }

    override fun authenticateJwtCompletable(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck?,
    ): CompletableFuture> =
        coroutineScope.async {
            authenticateJwt(jwt, maxTokenAgeSeconds, authorizationCheck)
        }.asCompletableFuture()

    override suspend fun authenticateJwtLocal(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck?,
        leeway: Int,
    ): StytchResult {
        return try {
            val jwtClaims =
                parseJWTClaims(
                    jwt = jwt,
                    jwtOptions = jwtOptions,
                    jwksClient = jwksClient,
                    options =
                        ParseJWTClaimsOptions(
                            leeway = leeway,
                            maxTokenAgeSeconds = maxTokenAgeSeconds,
                        ),
                )
            val stytchSessionClaims = jwtClaims.payload.claimsMap["https://stytch.com/session"] as? Map<*, *>
            val stytchSessionClaim =
                stytchSessionClaims?.let {
                    val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
                    val adapter: JsonAdapter> = moshi.adapter(type)
                    moshi.adapter(StytchSessionClaim::class.java).fromJson(adapter.toJson(it))
                } ?: throw JWTException.JwtMissingClaims
            val orgSessionClaims = jwtClaims.payload.claimsMap["https://stytch.com/organization"] as? Map<*, *>
            val organizationId = orgSessionClaims?.get("organization_id") as String
            if (authorizationCheck != null) {
                if (stytchSessionClaim.roles == null) {
                    throw JWTException.MissingRolesClaim
                }

                policyCache.performAuthorizationCheck(
                    subjectRoles = stytchSessionClaim.roles,
                    subjectOrgId = organizationId,
                    authorizationCheck = authorizationCheck,
                )
            }

            return StytchResult.Success(
                MemberSession(
                    memberSessionId = stytchSessionClaim.id,
                    memberId = jwtClaims.payload.subject,
                    organizationId = organizationId,
                    authenticationFactors = stytchSessionClaim.authenticationFactors,
                    startedAt = Instant.parse(stytchSessionClaim.startedAt),
                    lastAccessedAt = Instant.parse(stytchSessionClaim.lastAccessedAt),
                    expiresAt = Instant.parse(stytchSessionClaim.expiresAt),
                    customClaims = jwtClaims.customClaims,
                    roles = stytchSessionClaim.roles ?: emptyList(),
                ),
            )
        } catch (e: JWTException.JwtTooOld) {
            StytchResult.Error(StytchException.Critical(e))
        } catch (e: JWTException.JwtMissingClaims) {
            StytchResult.Error(StytchException.Critical(e))
        } catch (e: Exception) {
            StytchResult.Error(StytchException.Critical(JWTException.JwtError(e)))
        }
    }

    override fun authenticateJwtLocal(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck?,
        leeway: Int,
        callback: (StytchResult) -> Unit,
    ) {
        coroutineScope.launch {
            callback(authenticateJwtLocal(jwt, maxTokenAgeSeconds, authorizationCheck, leeway))
        }
    }

    override fun authenticateJwtLocalCompletable(
        jwt: String,
        maxTokenAgeSeconds: Int?,
        authorizationCheck: AuthorizationCheck?,
        leeway: Int,
    ): CompletableFuture> =
        coroutineScope.async {
            authenticateJwtLocal(jwt, maxTokenAgeSeconds, authorizationCheck, leeway)
        }.asCompletableFuture()
    // ENDMANUAL(authenticateJWT_impl)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy