systemprompt-oauth 0.10.0

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
//! CIMD metadata semantic validator.

use super::fetcher::CimdFetcher;
use crate::error::OauthResult as Result;
use crate::models::cimd::ClientValidation;
use crate::repository::oauth::OAuthRepository;
use std::sync::Arc;
use systemprompt_database::Database;
use systemprompt_identifiers::ClientId;

#[derive(Debug)]
pub struct ClientValidator {
    dcr_repo: OAuthRepository,
    cimd_fetcher: CimdFetcher,
}

impl ClientValidator {
    pub fn new(db_pool: &Arc<Database>) -> Result<Self> {
        Ok(Self {
            dcr_repo: OAuthRepository::new(db_pool)?,
            cimd_fetcher: CimdFetcher::new()?,
        })
    }

    pub async fn validate_client(
        &self,
        client_id: &ClientId,
        redirect_uri: Option<&str>,
    ) -> Result<ClientValidation> {
        match client_id.client_type() {
            systemprompt_identifiers::ClientType::Cimd => {
                self.validate_cimd(client_id, redirect_uri).await
            },
            systemprompt_identifiers::ClientType::FirstParty => Ok(ClientValidation::FirstParty {
                client_id: client_id.clone(),
            }),
            systemprompt_identifiers::ClientType::ThirdParty => self.validate_dcr(client_id).await,
            systemprompt_identifiers::ClientType::System => Ok(ClientValidation::System {
                client_id: client_id.clone(),
            }),
            systemprompt_identifiers::ClientType::Unknown => {
                Err(crate::error::OauthError::Internal(format!(
                    "Invalid client_id format: '{client_id}'. Expected patterns:\n- https://* \
                     (CIMD decentralized client)\n- sp_* (first-party systemprompt.io client)\n- \
                     client_* (third-party registered client)\n- sys_* (internal system service)"
                )))
            },
        }
    }

    async fn validate_cimd(
        &self,
        client_id: &ClientId,
        redirect_uri: Option<&str>,
    ) -> Result<ClientValidation> {
        let metadata = self.cimd_fetcher.fetch_metadata(client_id).await?;

        if let Some(uri) = redirect_uri {
            if !metadata.has_redirect_uri(uri) {
                return Err(crate::error::OauthError::Internal(format!(
                    "redirect_uri '{uri}' not registered in CIMD metadata for {client_id}"
                )));
            }
        }

        Ok(ClientValidation::Cimd {
            client_id: client_id.clone(),
            metadata: Box::new(metadata),
        })
    }

    async fn validate_dcr(&self, client_id: &ClientId) -> Result<ClientValidation> {
        let client = self.dcr_repo.find_client_by_id(client_id).await?;

        if client.is_none() {
            return Err(crate::error::OauthError::Internal(format!(
                "DCR client_id '{client_id}' not found in oauth_clients table"
            )));
        }

        Ok(ClientValidation::Dcr {
            client_id: client_id.clone(),
        })
    }
}