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
88 lines
2.8 KiB
Python
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")
|