systemprompt-oauth 0.10.2

OAuth 2.0 / OIDC with PKCE, token introspection, and audience/issuer validation for systemprompt.io AI governance infrastructure. WebAuthn and JWT auth for the MCP governance pipeline.
Documentation
//! OAuth client INSERT helpers.

use super::ClientRepository;
use crate::error::OauthResult as Result;
use crate::models::{GrantType, ResponseType};
use sqlx::{PgConnection, Postgres, Transaction};
use systemprompt_identifiers::ClientId;

pub(super) struct InsertRelatedData<'a> {
    pub client_id: &'a ClientId,
    pub redirect_uris: &'a [String],
    pub grant_types: Option<&'a [String]>,
    pub response_types: Option<&'a [String]>,
    pub scopes: &'a [String],
    pub contacts: Option<&'a [String]>,
}

impl ClientRepository {
    #[inline(never)]
    pub(super) async fn delete_related_data(
        tx: &mut Transaction<'_, Postgres>,
        client_id: &ClientId,
    ) -> Result<()> {
        let client_id_str = client_id.as_str();
        sqlx::query!(
            "DELETE FROM oauth_client_redirect_uris WHERE client_id = $1",
            client_id_str
        )
        .execute(&mut **tx)
        .await?;
        sqlx::query!(
            "DELETE FROM oauth_client_grant_types WHERE client_id = $1",
            client_id_str
        )
        .execute(&mut **tx)
        .await?;
        sqlx::query!(
            "DELETE FROM oauth_client_response_types WHERE client_id = $1",
            client_id_str
        )
        .execute(&mut **tx)
        .await?;
        sqlx::query!(
            "DELETE FROM oauth_client_scopes WHERE client_id = $1",
            client_id_str
        )
        .execute(&mut **tx)
        .await?;
        sqlx::query!(
            "DELETE FROM oauth_client_contacts WHERE client_id = $1",
            client_id_str
        )
        .execute(&mut **tx)
        .await?;
        Ok(())
    }

    #[inline(never)]
    pub(super) async fn insert_related_data(
        tx: &mut Transaction<'_, Postgres>,
        data: InsertRelatedData<'_>,
    ) -> Result<()> {
        Self::insert_redirect_uris(tx, data.client_id, data.redirect_uris).await?;
        Self::insert_grant_types(tx, data.client_id, data.grant_types).await?;
        Self::insert_response_types(tx, data.client_id, data.response_types).await?;
        Self::insert_scopes(tx, data.client_id, data.scopes).await?;
        Self::insert_contacts(tx, data.client_id, data.contacts).await?;
        Ok(())
    }

    #[inline(never)]
    async fn insert_redirect_uris(
        conn: &mut PgConnection,
        client_id: &ClientId,
        redirect_uris: &[String],
    ) -> Result<()> {
        if redirect_uris.is_empty() {
            return Ok(());
        }
        let client_id_str = client_id.as_str();
        sqlx::query!(
            "INSERT INTO oauth_client_redirect_uris (client_id, redirect_uri, is_primary)
             SELECT $1, u.uri, u.ord = 1
             FROM unnest($2::text[]) WITH ORDINALITY AS u(uri, ord)",
            client_id_str,
            redirect_uris
        )
        .execute(&mut *conn)
        .await?;
        Ok(())
    }

    #[inline(never)]
    async fn insert_grant_types(
        conn: &mut PgConnection,
        client_id: &ClientId,
        grant_types: Option<&[String]>,
    ) -> Result<()> {
        let default_grant_types: Vec<String> = GrantType::default_grant_types()
            .iter()
            .map(|s| (*s).to_string())
            .collect();
        let grant_types_list = grant_types.unwrap_or(&default_grant_types);
        if grant_types_list.is_empty() {
            return Ok(());
        }
        let client_id_str = client_id.as_str();
        sqlx::query!(
            "INSERT INTO oauth_client_grant_types (client_id, grant_type)
             SELECT $1, unnest($2::text[])",
            client_id_str,
            grant_types_list
        )
        .execute(&mut *conn)
        .await?;
        Ok(())
    }

    #[inline(never)]
    async fn insert_response_types(
        conn: &mut PgConnection,
        client_id: &ClientId,
        response_types: Option<&[String]>,
    ) -> Result<()> {
        let default_response_types = vec![ResponseType::Code.to_string()];
        let response_types_list = response_types.unwrap_or(&default_response_types);
        if response_types_list.is_empty() {
            return Ok(());
        }
        let client_id_str = client_id.as_str();
        sqlx::query!(
            "INSERT INTO oauth_client_response_types (client_id, response_type)
             SELECT $1, unnest($2::text[])",
            client_id_str,
            response_types_list
        )
        .execute(&mut *conn)
        .await?;
        Ok(())
    }

    #[inline(never)]
    async fn insert_scopes(
        conn: &mut PgConnection,
        client_id: &ClientId,
        scopes: &[String],
    ) -> Result<()> {
        if scopes.is_empty() {
            return Ok(());
        }
        let client_id_str = client_id.as_str();
        sqlx::query!(
            "INSERT INTO oauth_client_scopes (client_id, scope)
             SELECT $1, unnest($2::text[])",
            client_id_str,
            scopes
        )
        .execute(&mut *conn)
        .await?;
        Ok(())
    }

    #[inline(never)]
    async fn insert_contacts(
        conn: &mut PgConnection,
        client_id: &ClientId,
        contacts: Option<&[String]>,
    ) -> Result<()> {
        let Some(contact_list) = contacts else {
            return Ok(());
        };
        if contact_list.is_empty() {
            return Ok(());
        }
        let client_id_str = client_id.as_str();
        sqlx::query!(
            "INSERT INTO oauth_client_contacts (client_id, contact_email)
             SELECT $1, unnest($2::text[])",
            client_id_str,
            contact_list
        )
        .execute(&mut *conn)
        .await?;
        Ok(())
    }
}