"""MCP OAuth 2.1 — auth_oauth_clients (dynamically-registered public clients). One table for RFC 7591 dynamic client registration. No FK to identity_persons (clients are app registrations, not user-owned), so no TiDB-FK collation concerns. Authorization codes + refresh-token JTIs live in Redis (short-lived / single-use), not here. Idempotent. Revision ID: 325 Revises: 324 """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "325" down_revision = "324" branch_labels = None depends_on = None TABLE = "auth_oauth_clients" def _table_exists(bind, name: str) -> bool: return bind.execute( sa.text( "SELECT 1 FROM information_schema.tables " "WHERE table_schema = DATABASE() AND table_name = :n" ), {"n": name}, ).scalar() is not None def upgrade() -> None: bind = op.get_bind() if _table_exists(bind, TABLE): return op.create_table( TABLE, sa.Column("id", sa.CHAR(36), primary_key=True), sa.Column("client_id", sa.String(64), nullable=False), sa.Column("client_name", sa.String(255)), sa.Column("redirect_uris", sa.JSON, nullable=False), sa.Column("grant_types", sa.JSON), sa.Column("response_types", sa.JSON), sa.Column("scope", sa.String(255)), sa.Column( "token_endpoint_auth_method", sa.String(32), nullable=False, server_default="none", ), sa.Column("disabled_at", sa.DateTime(timezone=True)), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column("created_ip", sa.String(64)), sa.Column("created_user_agent", sa.String(512)), sa.UniqueConstraint("client_id", name="uq_auth_oauth_clients_client_id"), ) def downgrade() -> None: bind = op.get_bind() if _table_exists(bind, TABLE): op.drop_table(TABLE)