mantimon-tcg/backend/app/schemas/auth.py
Cal Corum 996c43fbd9 Implement Phase 2: Authentication system
Complete OAuth-based authentication with JWT session management:

Core Services:
- JWT service for access/refresh token creation and verification
- Token store with Redis-backed refresh token revocation
- User service for CRUD operations and OAuth-based creation
- Google and Discord OAuth services with full flow support

API Endpoints:
- GET /api/auth/{google,discord} - Start OAuth flows
- GET /api/auth/{google,discord}/callback - Handle OAuth callbacks
- POST /api/auth/refresh - Exchange refresh token for new access token
- POST /api/auth/logout - Revoke single refresh token
- POST /api/auth/logout-all - Revoke all user sessions
- GET/PATCH /api/users/me - User profile management
- GET /api/users/me/linked-accounts - List OAuth providers
- GET /api/users/me/sessions - Count active sessions

Infrastructure:
- Pydantic schemas for auth/user request/response models
- FastAPI dependencies (get_current_user, get_current_premium_user)
- OAuthLinkedAccount model for multi-provider support
- Alembic migration for oauth_linked_accounts table

Dependencies added: email-validator, fakeredis (dev), respx (dev)

84 new tests, 1058 total passing
2026-01-27 21:49:59 -06:00

88 lines
2.8 KiB
Python

"""Authentication schemas for Mantimon TCG.
This module defines Pydantic models for JWT tokens and authentication
responses used throughout the auth system.
Example:
token_payload = TokenPayload(
sub="550e8400-e29b-41d4-a716-446655440000",
exp=datetime.now(UTC) + timedelta(minutes=30),
iat=datetime.now(UTC),
type="access"
)
"""
from datetime import datetime
from enum import Enum
from pydantic import BaseModel, Field
class TokenType(str, Enum):
"""Type of JWT token."""
ACCESS = "access"
REFRESH = "refresh"
class TokenPayload(BaseModel):
"""JWT token payload structure.
Attributes:
sub: Subject - the user ID as a string (UUID format).
exp: Expiration timestamp.
iat: Issued-at timestamp.
type: Token type (access or refresh).
jti: JWT ID - unique identifier for refresh token tracking.
"""
sub: str = Field(..., description="User ID (UUID as string)")
exp: datetime = Field(..., description="Token expiration timestamp")
iat: datetime = Field(..., description="Token issued-at timestamp")
type: TokenType = Field(..., description="Token type (access/refresh)")
jti: str | None = Field(default=None, description="JWT ID for refresh token tracking")
class TokenResponse(BaseModel):
"""Response containing JWT tokens after successful authentication.
Attributes:
access_token: Short-lived JWT for API authentication.
refresh_token: Longer-lived JWT for obtaining new access tokens.
token_type: Always "bearer" for OAuth 2.0 compatibility.
expires_in: Access token lifetime in seconds.
"""
access_token: str = Field(..., description="JWT access token")
refresh_token: str = Field(..., description="JWT refresh token")
token_type: str = Field(default="bearer", description="Token type (always bearer)")
expires_in: int = Field(..., description="Access token lifetime in seconds")
class RefreshTokenRequest(BaseModel):
"""Request to refresh an access token.
Attributes:
refresh_token: The refresh token to exchange for a new access token.
"""
refresh_token: str = Field(..., description="Refresh token to exchange")
class OAuthState(BaseModel):
"""OAuth state parameter for CSRF protection.
Stored in Redis with short TTL during OAuth flow.
Attributes:
state: Random string for CSRF protection.
redirect_uri: Where to redirect after OAuth callback.
provider: OAuth provider name.
created_at: When the state was created.
"""
state: str = Field(..., description="Random CSRF protection string")
redirect_uri: str = Field(..., description="Post-auth redirect URI")
provider: str = Field(..., description="OAuth provider (google, discord)")
created_at: datetime = Field(..., description="State creation timestamp")