Add detailed Phase 2 (Authentication) project plan
Defines 15 tasks covering OAuth login (Google/Discord), JWT session management, user services, and API endpoints for player authentication at play.mantimon.com. Key components: - JWT utilities with access/refresh token pattern - Redis-backed refresh token storage for revocation - Google and Discord OAuth services - FastAPI auth dependencies (get_current_user, etc.) - Account linking support (multiple OAuth providers per user) - Premium subscription tracking Estimated: 24 hours across 1-2 weeks
This commit is contained in:
parent
b78236ac49
commit
4ddc9b8c30
454
backend/project_plans/PHASE_2_AUTH.json
Normal file
454
backend/project_plans/PHASE_2_AUTH.json
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"created": "2026-01-27",
|
||||||
|
"lastUpdated": "2026-01-27",
|
||||||
|
"planType": "phase",
|
||||||
|
"phaseId": "PHASE_2",
|
||||||
|
"phaseName": "Authentication",
|
||||||
|
"description": "OAuth login (Google, Discord), JWT session management, user management, premium tier tracking",
|
||||||
|
"totalEstimatedHours": 24,
|
||||||
|
"totalTasks": 15,
|
||||||
|
"completedTasks": 0,
|
||||||
|
"masterPlan": "../PROJECT_PLAN_MASTER.json"
|
||||||
|
},
|
||||||
|
|
||||||
|
"goals": [
|
||||||
|
"Implement OAuth 2.0 authentication with Google and Discord providers",
|
||||||
|
"Create JWT-based session management with access/refresh token pattern",
|
||||||
|
"Build user management service with create, read, update operations",
|
||||||
|
"Implement FastAPI dependencies for protected endpoints",
|
||||||
|
"Support account linking (multiple OAuth providers per user)",
|
||||||
|
"Track premium subscription status with expiration dates"
|
||||||
|
],
|
||||||
|
|
||||||
|
"architectureNotes": {
|
||||||
|
"tokenStrategy": {
|
||||||
|
"accessToken": "Short-lived JWT (30 min default), contains user_id",
|
||||||
|
"refreshToken": "Longer-lived JWT (7 days default), stored in Redis for revocation",
|
||||||
|
"storage": "Refresh tokens tracked in Redis for logout/revocation support"
|
||||||
|
},
|
||||||
|
"oauthFlow": {
|
||||||
|
"pattern": "Authorization Code Flow with PKCE",
|
||||||
|
"callback": "Backend receives code, exchanges for tokens, creates/updates user",
|
||||||
|
"security": "Never store OAuth provider tokens, only OAuth ID"
|
||||||
|
},
|
||||||
|
"accountLinking": {
|
||||||
|
"strategy": "Email-based matching",
|
||||||
|
"flow": "If user exists with same email, add OAuth provider to existing account"
|
||||||
|
},
|
||||||
|
"existingInfrastructure": {
|
||||||
|
"config": "JWT and OAuth settings already in app/config.py Settings class",
|
||||||
|
"dependencies": "python-jose, passlib, bcrypt already installed",
|
||||||
|
"userModel": "User model with OAuth fields already in app/db/models/user.py"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"directoryStructure": {
|
||||||
|
"schemas": "backend/app/schemas/",
|
||||||
|
"services": "backend/app/services/",
|
||||||
|
"api": "backend/app/api/",
|
||||||
|
"tests": "backend/tests/api/, backend/tests/services/"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"id": "AUTH-001",
|
||||||
|
"name": "Create Pydantic schemas for auth",
|
||||||
|
"description": "Define request/response models for authentication flows",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 1,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": [],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/schemas/__init__.py", "status": "pending"},
|
||||||
|
{"path": "app/schemas/auth.py", "status": "pending"},
|
||||||
|
{"path": "app/schemas/user.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"TokenPayload: sub (user_id), exp, iat, type (access/refresh)",
|
||||||
|
"TokenResponse: access_token, refresh_token, token_type, expires_in",
|
||||||
|
"UserResponse: id, email, display_name, avatar_url, is_premium, premium_until",
|
||||||
|
"UserCreate: internal model for user creation from OAuth",
|
||||||
|
"OAuthUserInfo: normalized structure for OAuth provider data",
|
||||||
|
"AccountLinkRequest: for linking additional OAuth providers"
|
||||||
|
],
|
||||||
|
"estimatedHours": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-002",
|
||||||
|
"name": "Create JWT utilities service",
|
||||||
|
"description": "Functions for creating and verifying JWT tokens",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 2,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-001"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/services/jwt_service.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"create_access_token(user_id: UUID) -> str - Uses settings.jwt_expire_minutes",
|
||||||
|
"create_refresh_token(user_id: UUID) -> str - Uses settings.jwt_refresh_expire_days",
|
||||||
|
"decode_token(token: str) -> TokenPayload - Validates and decodes",
|
||||||
|
"verify_token(token: str) -> UUID | None - Returns user_id or None if invalid",
|
||||||
|
"Uses python-jose with HS256 algorithm",
|
||||||
|
"All timing uses datetime.now(UTC) per project standards"
|
||||||
|
],
|
||||||
|
"estimatedHours": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-003",
|
||||||
|
"name": "Create refresh token Redis storage",
|
||||||
|
"description": "Redis-based storage for refresh token tracking and revocation",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 3,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-002"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/services/token_store.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Key format: refresh_token:{user_id}:{jti} -> expiration timestamp",
|
||||||
|
"store_refresh_token(user_id, jti, expires_at) - Store with TTL",
|
||||||
|
"is_token_valid(user_id, jti) -> bool - Check if not revoked",
|
||||||
|
"revoke_token(user_id, jti) - Delete specific token",
|
||||||
|
"revoke_all_user_tokens(user_id) - Logout from all devices",
|
||||||
|
"Uses existing Redis connection from app/db/redis.py"
|
||||||
|
],
|
||||||
|
"estimatedHours": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-004",
|
||||||
|
"name": "Create UserService",
|
||||||
|
"description": "Service layer for user CRUD operations",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 4,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-001"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/services/user_service.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"get_user_by_id(db, user_id: UUID) -> User | None",
|
||||||
|
"get_user_by_email(db, email: str) -> User | None",
|
||||||
|
"get_user_by_oauth(db, provider: str, oauth_id: str) -> User | None",
|
||||||
|
"create_user(db, user_data: UserCreate) -> User",
|
||||||
|
"update_last_login(db, user_id: UUID) -> None",
|
||||||
|
"link_oauth_provider(db, user_id: UUID, provider: str, oauth_id: str) -> User",
|
||||||
|
"update_premium_status(db, user_id: UUID, premium_until: datetime | None) -> User",
|
||||||
|
"All operations are async using SQLAlchemy async session"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-005",
|
||||||
|
"name": "Create OAuthLinkedAccount model",
|
||||||
|
"description": "Database model for multiple OAuth providers per user (account linking)",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 5,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": [],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/db/models/oauth_account.py", "status": "pending"},
|
||||||
|
{"path": "app/db/migrations/versions/xxx_add_oauth_accounts.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Fields: id, user_id (FK), provider, oauth_id, linked_at",
|
||||||
|
"Unique constraint on (provider, oauth_id)",
|
||||||
|
"Migrate existing User.oauth_provider/oauth_id to this table",
|
||||||
|
"Keep User.oauth_provider/oauth_id as 'primary' for backward compat",
|
||||||
|
"Relationship: User.linked_accounts -> list[OAuthLinkedAccount]"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-006",
|
||||||
|
"name": "Create Google OAuth service",
|
||||||
|
"description": "Handle Google OAuth authorization code flow",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 6,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-004"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/services/oauth/google.py", "status": "pending"},
|
||||||
|
{"path": "app/services/oauth/__init__.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"get_authorization_url(redirect_uri, state) -> str",
|
||||||
|
"exchange_code_for_tokens(code, redirect_uri) -> GoogleTokens",
|
||||||
|
"get_user_info(access_token) -> OAuthUserInfo",
|
||||||
|
"Uses httpx for async HTTP requests",
|
||||||
|
"Validates state parameter to prevent CSRF",
|
||||||
|
"Google OAuth endpoints: accounts.google.com/o/oauth2/v2/auth, oauth2.googleapis.com/token",
|
||||||
|
"User info endpoint: www.googleapis.com/oauth2/v2/userinfo"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-007",
|
||||||
|
"name": "Create Discord OAuth service",
|
||||||
|
"description": "Handle Discord OAuth authorization code flow",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 7,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-004"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/services/oauth/discord.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"get_authorization_url(redirect_uri, state) -> str",
|
||||||
|
"exchange_code_for_tokens(code, redirect_uri) -> DiscordTokens",
|
||||||
|
"get_user_info(access_token) -> OAuthUserInfo",
|
||||||
|
"Uses httpx for async HTTP requests",
|
||||||
|
"Discord OAuth endpoints: discord.com/api/oauth2/authorize, discord.com/api/oauth2/token",
|
||||||
|
"User info endpoint: discord.com/api/users/@me",
|
||||||
|
"Avatar URL construction from user ID and avatar hash"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-008",
|
||||||
|
"name": "Create FastAPI auth dependencies",
|
||||||
|
"description": "Dependency injection for protected endpoints",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 8,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-002", "AUTH-003", "AUTH-004"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/api/__init__.py", "status": "pending"},
|
||||||
|
{"path": "app/api/deps.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"OAuth2PasswordBearer scheme for token extraction",
|
||||||
|
"get_current_user(token) -> User - Validates token, fetches user",
|
||||||
|
"get_current_active_user() -> User - Ensures user exists and is active",
|
||||||
|
"get_current_premium_user() -> User - Requires active premium subscription",
|
||||||
|
"get_optional_user() -> User | None - For endpoints that work with/without auth",
|
||||||
|
"Proper error responses: 401 Unauthorized, 403 Forbidden"
|
||||||
|
],
|
||||||
|
"estimatedHours": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-009",
|
||||||
|
"name": "Create auth API router",
|
||||||
|
"description": "REST endpoints for OAuth login, token refresh, logout",
|
||||||
|
"category": "critical",
|
||||||
|
"priority": 9,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-006", "AUTH-007", "AUTH-008"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/api/auth.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"GET /auth/google - Redirects to Google OAuth consent screen",
|
||||||
|
"GET /auth/google/callback - Handles OAuth callback, returns tokens",
|
||||||
|
"GET /auth/discord - Redirects to Discord OAuth consent screen",
|
||||||
|
"GET /auth/discord/callback - Handles OAuth callback, returns tokens",
|
||||||
|
"POST /auth/refresh - Exchange refresh token for new access token",
|
||||||
|
"POST /auth/logout - Revoke refresh token",
|
||||||
|
"State parameter stored in Redis with short TTL for CSRF protection"
|
||||||
|
],
|
||||||
|
"estimatedHours": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-010",
|
||||||
|
"name": "Create user API router",
|
||||||
|
"description": "REST endpoints for user profile and account management",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 10,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-008", "AUTH-004"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/api/users.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"GET /users/me - Get current user profile",
|
||||||
|
"PATCH /users/me - Update display_name, avatar_url",
|
||||||
|
"GET /users/me/linked-accounts - List linked OAuth providers",
|
||||||
|
"POST /users/me/link/{provider} - Start account linking flow",
|
||||||
|
"DELETE /users/me/link/{provider} - Unlink OAuth provider (if not last)",
|
||||||
|
"All endpoints require authentication via get_current_user dependency"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-011",
|
||||||
|
"name": "Integrate routers in main.py",
|
||||||
|
"description": "Mount auth and user routers, add any required middleware",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 11,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-009", "AUTH-010"],
|
||||||
|
"files": [
|
||||||
|
{"path": "app/main.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Include auth router: prefix='/api/auth', tags=['auth']",
|
||||||
|
"Include users router: prefix='/api/users', tags=['users']",
|
||||||
|
"Remove TODO comments for router integration",
|
||||||
|
"Verify CORS allows credentials for token cookies (if used)"
|
||||||
|
],
|
||||||
|
"estimatedHours": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-012",
|
||||||
|
"name": "Create JWT service tests",
|
||||||
|
"description": "Unit tests for token creation and verification",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 12,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-002"],
|
||||||
|
"files": [
|
||||||
|
{"path": "tests/services/test_jwt_service.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Test create_access_token returns valid JWT",
|
||||||
|
"Test create_refresh_token returns valid JWT with correct type",
|
||||||
|
"Test decode_token extracts correct payload",
|
||||||
|
"Test verify_token returns None for expired tokens",
|
||||||
|
"Test verify_token returns None for invalid signatures",
|
||||||
|
"Test token expiration times are correct"
|
||||||
|
],
|
||||||
|
"estimatedHours": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-013",
|
||||||
|
"name": "Create UserService tests",
|
||||||
|
"description": "Integration tests for user CRUD operations",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 13,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-004"],
|
||||||
|
"files": [
|
||||||
|
{"path": "tests/services/test_user_service.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Test get_user_by_id returns user or None",
|
||||||
|
"Test get_user_by_oauth finds by provider+oauth_id",
|
||||||
|
"Test create_user creates with correct fields",
|
||||||
|
"Test update_last_login updates timestamp",
|
||||||
|
"Test link_oauth_provider adds linked account",
|
||||||
|
"Test premium status update",
|
||||||
|
"Uses real Postgres via testcontainers pattern"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-014",
|
||||||
|
"name": "Create OAuth service tests",
|
||||||
|
"description": "Unit tests for OAuth flows with mocked HTTP",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 14,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-006", "AUTH-007"],
|
||||||
|
"files": [
|
||||||
|
{"path": "tests/services/oauth/test_google.py", "status": "pending"},
|
||||||
|
{"path": "tests/services/oauth/test_discord.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Mock httpx responses for token exchange",
|
||||||
|
"Mock httpx responses for user info",
|
||||||
|
"Test authorization URL construction",
|
||||||
|
"Test error handling for invalid codes",
|
||||||
|
"Test OAuthUserInfo normalization",
|
||||||
|
"Uses respx or pytest-httpx for mocking"
|
||||||
|
],
|
||||||
|
"estimatedHours": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AUTH-015",
|
||||||
|
"name": "Create auth API endpoint tests",
|
||||||
|
"description": "Integration tests for auth endpoints",
|
||||||
|
"category": "high",
|
||||||
|
"priority": 15,
|
||||||
|
"completed": false,
|
||||||
|
"dependencies": ["AUTH-009", "AUTH-010"],
|
||||||
|
"files": [
|
||||||
|
{"path": "tests/api/__init__.py", "status": "pending"},
|
||||||
|
{"path": "tests/api/conftest.py", "status": "pending"},
|
||||||
|
{"path": "tests/api/test_auth.py", "status": "pending"},
|
||||||
|
{"path": "tests/api/test_users.py", "status": "pending"}
|
||||||
|
],
|
||||||
|
"details": [
|
||||||
|
"Test OAuth redirect returns correct URL",
|
||||||
|
"Test callback with mocked OAuth creates user and returns tokens",
|
||||||
|
"Test refresh endpoint returns new access token",
|
||||||
|
"Test logout revokes refresh token",
|
||||||
|
"Test /users/me returns current user",
|
||||||
|
"Test /users/me update works",
|
||||||
|
"Uses TestClient with dependency overrides"
|
||||||
|
],
|
||||||
|
"estimatedHours": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"testingStrategy": {
|
||||||
|
"approach": "Unit tests for services, integration tests for API endpoints",
|
||||||
|
"mocking": "httpx responses mocked for OAuth providers, fakeredis for token store",
|
||||||
|
"database": "Real Postgres via testcontainers for UserService tests",
|
||||||
|
"coverage": "Target 90%+ coverage on new auth code"
|
||||||
|
},
|
||||||
|
|
||||||
|
"weeklyRoadmap": {
|
||||||
|
"week1": {
|
||||||
|
"theme": "Core Services",
|
||||||
|
"tasks": ["AUTH-001", "AUTH-002", "AUTH-003", "AUTH-004", "AUTH-005"],
|
||||||
|
"goals": ["Schemas defined", "JWT working", "UserService complete"]
|
||||||
|
},
|
||||||
|
"week2": {
|
||||||
|
"theme": "OAuth + API",
|
||||||
|
"tasks": ["AUTH-006", "AUTH-007", "AUTH-008", "AUTH-009", "AUTH-010", "AUTH-011"],
|
||||||
|
"goals": ["OAuth flows working", "API endpoints complete", "Integration done"]
|
||||||
|
},
|
||||||
|
"week3": {
|
||||||
|
"theme": "Testing",
|
||||||
|
"tasks": ["AUTH-012", "AUTH-013", "AUTH-014", "AUTH-015"],
|
||||||
|
"goals": ["Full test coverage", "All tests passing"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
{"criterion": "User can login with Google OAuth and receive JWT tokens", "met": false},
|
||||||
|
{"criterion": "User can login with Discord OAuth and receive JWT tokens", "met": false},
|
||||||
|
{"criterion": "Access tokens expire after configured time", "met": false},
|
||||||
|
{"criterion": "Refresh tokens can be used to get new access tokens", "met": false},
|
||||||
|
{"criterion": "Logout revokes refresh token (cannot be reused)", "met": false},
|
||||||
|
{"criterion": "Protected endpoints return 401 without valid token", "met": false},
|
||||||
|
{"criterion": "User can link multiple OAuth providers to one account", "met": false},
|
||||||
|
{"criterion": "Premium status is tracked with expiration date", "met": false},
|
||||||
|
{"criterion": "All tests pass with high coverage", "met": false}
|
||||||
|
],
|
||||||
|
|
||||||
|
"securityConsiderations": [
|
||||||
|
"OAuth state parameter validated to prevent CSRF attacks",
|
||||||
|
"OAuth provider tokens never stored (only OAuth ID)",
|
||||||
|
"JWT secret key loaded from environment, never hardcoded",
|
||||||
|
"Refresh tokens stored in Redis with TTL for revocation support",
|
||||||
|
"Access tokens short-lived (30 min) to limit exposure",
|
||||||
|
"Rate limiting on auth endpoints (future enhancement)",
|
||||||
|
"HTTPS required in production for all auth endpoints"
|
||||||
|
],
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
"existing": [
|
||||||
|
"python-jose>=3.5.0 (already installed)",
|
||||||
|
"passlib>=1.7.4 (already installed)",
|
||||||
|
"bcrypt>=5.0.0 (already installed)"
|
||||||
|
],
|
||||||
|
"toAdd": [
|
||||||
|
"httpx>=0.25.0 (async HTTP client for OAuth)",
|
||||||
|
"respx>=0.20.0 (httpx mocking for tests)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"phase1Prerequisites": {
|
||||||
|
"met": [
|
||||||
|
"JWT configuration in Settings class",
|
||||||
|
"OAuth configuration in Settings class",
|
||||||
|
"User model with OAuth fields",
|
||||||
|
"python-jose dependency installed",
|
||||||
|
"Database session management",
|
||||||
|
"Redis connection utilities"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user