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
//! OAuth extension registration metadata.

use systemprompt_extension::prelude::*;

const MIGRATION_001_RFC8707_RESOURCE: &str = r"
ALTER TABLE oauth_auth_codes ADD COLUMN IF NOT EXISTS resource TEXT;
";

const MIGRATION_002_RENAME_COWORK_TO_BRIDGE: &str = r"
DO $$
BEGIN
    IF to_regclass('cowork_exchange_codes') IS NOT NULL
       AND to_regclass('bridge_exchange_codes') IS NULL THEN
        ALTER TABLE cowork_exchange_codes RENAME TO bridge_exchange_codes;
    END IF;
    IF to_regclass('idx_cowork_exchange_codes_user') IS NOT NULL THEN
        ALTER INDEX idx_cowork_exchange_codes_user RENAME TO idx_bridge_exchange_codes_user;
    END IF;
    IF to_regclass('idx_cowork_exchange_codes_active') IS NOT NULL THEN
        ALTER INDEX idx_cowork_exchange_codes_active RENAME TO idx_bridge_exchange_codes_active;
    END IF;
END $$;
";

#[derive(Debug, Clone, Copy, Default)]
pub struct OauthExtension;

impl Extension for OauthExtension {
    fn metadata(&self) -> ExtensionMetadata {
        ExtensionMetadata {
            id: "oauth",
            name: "OAuth",
            version: env!("CARGO_PKG_VERSION"),
        }
    }

    fn migration_weight(&self) -> u32 {
        300
    }

    fn schemas(&self) -> Vec<SchemaDefinition> {
        vec![
            SchemaDefinition::new("oauth_clients", include_str!("../schema/oauth_clients.sql"))
                .with_required_columns(vec![
                    "client_id".into(),
                    "client_name".into(),
                    "created_at".into(),
                ]),
            SchemaDefinition::new(
                "oauth_client_redirect_uris",
                include_str!("../schema/oauth_client_redirect_uris.sql"),
            )
            .with_required_columns(vec!["client_id".into(), "redirect_uri".into()]),
            SchemaDefinition::new(
                "oauth_client_grant_types",
                include_str!("../schema/oauth_client_grant_types.sql"),
            )
            .with_required_columns(vec!["client_id".into(), "grant_type".into()]),
            SchemaDefinition::new(
                "oauth_client_response_types",
                include_str!("../schema/oauth_client_response_types.sql"),
            )
            .with_required_columns(vec!["client_id".into(), "response_type".into()]),
            SchemaDefinition::new(
                "oauth_client_scopes",
                include_str!("../schema/oauth_client_scopes.sql"),
            )
            .with_required_columns(vec!["client_id".into(), "scope".into()]),
            SchemaDefinition::new(
                "oauth_client_contacts",
                include_str!("../schema/oauth_client_contacts.sql"),
            )
            .with_required_columns(vec!["client_id".into(), "contact_email".into()]),
            SchemaDefinition::new(
                "oauth_auth_codes",
                include_str!("../schema/oauth_auth_codes.sql"),
            )
            .with_required_columns(vec![
                "code".into(),
                "client_id".into(),
                "user_id".into(),
            ]),
            SchemaDefinition::new(
                "oauth_refresh_tokens",
                include_str!("../schema/oauth_refresh_tokens.sql"),
            )
            .with_required_columns(vec![
                "token_id".into(),
                "client_id".into(),
                "user_id".into(),
            ]),
            SchemaDefinition::new(
                "webauthn_credentials",
                include_str!("../schema/webauthn_credentials.sql"),
            )
            .with_required_columns(vec![
                "id".into(),
                "user_id".into(),
                "credential_id".into(),
            ]),
            SchemaDefinition::new(
                "webauthn_challenges",
                include_str!("../schema/webauthn_challenges.sql"),
            )
            .with_required_columns(vec!["challenge".into(), "user_id".into()]),
            SchemaDefinition::new(
                "webauthn_setup_tokens",
                include_str!("../schema/webauthn_setup_tokens.sql"),
            )
            .with_required_columns(vec![
                "id".into(),
                "user_id".into(),
                "token_hash".into(),
            ]),
            SchemaDefinition::new(
                "bridge_exchange_codes",
                include_str!("../schema/bridge_exchange_codes.sql"),
            )
            .with_required_columns(vec![
                "code_hash".into(),
                "user_id".into(),
                "expires_at".into(),
            ]),
            SchemaDefinition::new(
                "bridge_sessions",
                include_str!("../schema/bridge_sessions.sql"),
            )
            .with_required_columns(vec![
                "session_id".into(),
                "user_id".into(),
                "bridge_version".into(),
                "last_heartbeat_at".into(),
            ]),
            SchemaDefinition::new(
                "bridge_user_host_prefs",
                include_str!("../schema/bridge_user_host_prefs.sql"),
            )
            .with_required_columns(vec![
                "user_id".into(),
                "host_id".into(),
                "enabled".into(),
            ]),
        ]
    }

    fn dependencies(&self) -> Vec<&'static str> {
        vec!["users"]
    }

    fn migrations(&self) -> Vec<Migration> {
        vec![
            Migration::new(
                1,
                "add_rfc8707_resource_column",
                MIGRATION_001_RFC8707_RESOURCE,
            ),
            Migration::new(
                2,
                "rename_cowork_to_bridge",
                MIGRATION_002_RENAME_COWORK_TO_BRIDGE,
            ),
        ]
    }
}

register_extension!(OauthExtension);