Files
fast-next-template/backend/app/alembic/versions/f8c3d2e1a4b5_add_oauth_provider_models.py
Felipe Cardoso b3f0dd4005 Add full OAuth provider functionality and enhance flows
- Implemented OAuth 2.0 Authorization Server endpoints per RFCs, including token, introspection, revocation, and metadata discovery.
- Added user consent submission, listing, and revocation APIs alongside frontend integration for improved UX.
- Enforced stricter OAuth security measures (PKCE, state validation, scopes).
- Refactored schemas and services for consistency and expanded coverage of OAuth workflows.
- Updated documentation and type definitions for new API behaviors.
2025-11-26 13:23:44 +01:00

195 lines
6.3 KiB
Python
Executable File

"""Add OAuth provider models for MCP integration.
Revision ID: f8c3d2e1a4b5
Revises: d5a7b2c9e1f3
Create Date: 2025-01-15 10:00:00.000000
This migration adds tables for OAuth provider mode:
- oauth_authorization_codes: Temporary authorization codes
- oauth_provider_refresh_tokens: Long-lived refresh tokens
- oauth_consents: User consent records
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "f8c3d2e1a4b5"
down_revision = "d5a7b2c9e1f3"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Create oauth_authorization_codes table
op.create_table(
"oauth_authorization_codes",
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("code", sa.String(128), nullable=False),
sa.Column("client_id", sa.String(64), nullable=False),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("redirect_uri", sa.String(2048), nullable=False),
sa.Column("scope", sa.String(1000), nullable=False, server_default=""),
sa.Column("code_challenge", sa.String(128), nullable=True),
sa.Column("code_challenge_method", sa.String(10), nullable=True),
sa.Column("state", sa.String(256), nullable=True),
sa.Column("nonce", sa.String(256), nullable=True),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("used", sa.Boolean(), nullable=False, server_default="false"),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(
["client_id"],
["oauth_clients.client_id"],
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
"ix_oauth_authorization_codes_code",
"oauth_authorization_codes",
["code"],
unique=True,
)
op.create_index(
"ix_oauth_authorization_codes_expires_at",
"oauth_authorization_codes",
["expires_at"],
)
op.create_index(
"ix_oauth_authorization_codes_client_user",
"oauth_authorization_codes",
["client_id", "user_id"],
)
# Create oauth_provider_refresh_tokens table
op.create_table(
"oauth_provider_refresh_tokens",
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("token_hash", sa.String(64), nullable=False),
sa.Column("jti", sa.String(64), nullable=False),
sa.Column("client_id", sa.String(64), nullable=False),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("scope", sa.String(1000), nullable=False, server_default=""),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("revoked", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("device_info", sa.String(500), nullable=True),
sa.Column("ip_address", sa.String(45), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(
["client_id"],
["oauth_clients.client_id"],
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
"ix_oauth_provider_refresh_tokens_token_hash",
"oauth_provider_refresh_tokens",
["token_hash"],
unique=True,
)
op.create_index(
"ix_oauth_provider_refresh_tokens_jti",
"oauth_provider_refresh_tokens",
["jti"],
unique=True,
)
op.create_index(
"ix_oauth_provider_refresh_tokens_expires_at",
"oauth_provider_refresh_tokens",
["expires_at"],
)
op.create_index(
"ix_oauth_provider_refresh_tokens_client_user",
"oauth_provider_refresh_tokens",
["client_id", "user_id"],
)
op.create_index(
"ix_oauth_provider_refresh_tokens_user_revoked",
"oauth_provider_refresh_tokens",
["user_id", "revoked"],
)
op.create_index(
"ix_oauth_provider_refresh_tokens_revoked",
"oauth_provider_refresh_tokens",
["revoked"],
)
# Create oauth_consents table
op.create_table(
"oauth_consents",
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("client_id", sa.String(64), nullable=False),
sa.Column("granted_scopes", sa.String(1000), nullable=False, server_default=""),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(
["client_id"],
["oauth_clients.client_id"],
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
"ix_oauth_consents_user_client",
"oauth_consents",
["user_id", "client_id"],
unique=True,
)
def downgrade() -> None:
op.drop_table("oauth_consents")
op.drop_table("oauth_provider_refresh_tokens")
op.drop_table("oauth_authorization_codes")