mitlist/be/app/config.py
google-labs-jules[bot] 8034824c97 Fix: Resolve Google OAuth redirection issue
This commit addresses an issue where you, when clicking the "Continue with Google"
button, were redirected back to the login page instead of to Google's
authentication page.

The following changes were made:

1.  **Frontend Redirect:**
    *   Modified `fe/src/components/SocialLoginButtons.vue` to make the "Continue with Google" button redirect to the correct backend API endpoint (`/auth/google/login`) using the configured `API_BASE_URL`.

2.  **Backend Route Confirmation:**
    *   Verified that the backend OAuth routes in `be/app/api/auth/oauth.py` are correctly included in `be/app/main.py` under the `/auth` prefix, making them accessible.

3.  **OAuth Credentials Configuration:**
    *   Added `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` placeholders to `env.production.template` to guide you in setting up your OAuth credentials.
    *   Added instructional comments in `be/app/config.py` regarding the necessity of these environment variables and the correct configuration of `GOOGLE_REDIRECT_URI`.

With these changes, and assuming the necessary Google Cloud OAuth credentials
(Client ID, Client Secret) and redirect URIs are correctly configured in the
environment, the Google OAuth flow should now function as expected.
2025-06-02 00:19:26 +02:00

207 lines
8.2 KiB
Python

# app/config.py
import os
from pydantic_settings import BaseSettings
from dotenv import load_dotenv
import logging
import secrets
from typing import List
load_dotenv()
logger = logging.getLogger(__name__)
class Settings(BaseSettings):
DATABASE_URL: str | None = None
GEMINI_API_KEY: str | None = None
SENTRY_DSN: str | None = None # Sentry DSN for error tracking
# --- Environment Settings ---
ENVIRONMENT: str = "development" # development, staging, production
# --- JWT Settings --- (SECRET_KEY is used by FastAPI-Users)
SECRET_KEY: str # Must be set via environment variable
TOKEN_TYPE: str = "bearer" # Default token type for JWT authentication
# FastAPI-Users handles JWT algorithm internally
# --- OCR Settings ---
MAX_FILE_SIZE_MB: int = 10 # Maximum allowed file size for OCR processing
ALLOWED_IMAGE_TYPES: list[str] = ["image/jpeg", "image/png", "image/webp"] # Supported image formats
OCR_ITEM_EXTRACTION_PROMPT: str = """
Extract the shopping list items from this image.
List each distinct item on a new line.
Ignore prices, quantities, store names, discounts, taxes, totals, and other non-item text.
Focus only on the names of the products or items to be purchased.
Add 2 underscores before and after the item name, if it is struck through.
If the image does not appear to be a shopping list or receipt, state that clearly.
Example output for a grocery list:
Milk
Eggs
Bread
__Apples__
Organic Bananas
"""
# --- OCR Error Messages ---
OCR_SERVICE_UNAVAILABLE: str = "OCR service is currently unavailable. Please try again later."
OCR_SERVICE_CONFIG_ERROR: str = "OCR service configuration error. Please contact support."
OCR_UNEXPECTED_ERROR: str = "An unexpected error occurred during OCR processing."
OCR_QUOTA_EXCEEDED: str = "OCR service quota exceeded. Please try again later."
OCR_INVALID_FILE_TYPE: str = "Invalid file type. Supported types: {types}"
OCR_FILE_TOO_LARGE: str = "File too large. Maximum size: {size}MB"
OCR_PROCESSING_ERROR: str = "Error processing image: {detail}"
# --- Gemini AI Settings ---
GEMINI_MODEL_NAME: str = "gemini-2.0-flash" # The model to use for OCR
GEMINI_SAFETY_SETTINGS: dict = {
"HARM_CATEGORY_HATE_SPEECH": "BLOCK_MEDIUM_AND_ABOVE",
"HARM_CATEGORY_DANGEROUS_CONTENT": "BLOCK_MEDIUM_AND_ABOVE",
"HARM_CATEGORY_HARASSMENT": "BLOCK_MEDIUM_AND_ABOVE",
"HARM_CATEGORY_SEXUALLY_EXPLICIT": "BLOCK_MEDIUM_AND_ABOVE",
}
GEMINI_GENERATION_CONFIG: dict = {
"candidate_count": 1,
"max_output_tokens": 2048,
"temperature": 0.9,
"top_p": 1,
"top_k": 1
}
# --- API Settings ---
API_PREFIX: str = "/api" # Base path for all API endpoints
API_OPENAPI_URL: str = "/api/openapi.json"
API_DOCS_URL: str = "/api/docs"
API_REDOC_URL: str = "/api/redoc"
# CORS Origins - environment dependent
CORS_ORIGINS: str = "http://localhost:5173,http://localhost:5174,http://localhost:8000,http://127.0.0.1:5173,http://127.0.0.1:5174,http://127.0.0.1:8000"
FRONTEND_URL: str = "http://localhost:5173" # URL for the frontend application
# --- API Metadata ---
API_TITLE: str = "Shared Lists API"
API_DESCRIPTION: str = "API for managing shared shopping lists, OCR, and cost splitting."
API_VERSION: str = "0.1.0"
ROOT_MESSAGE: str = "Welcome to the Shared Lists API! Docs available at /api/docs"
# --- Logging Settings ---
LOG_LEVEL: str = "WARNING"
LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# --- Health Check Settings ---
HEALTH_STATUS_OK: str = "ok"
HEALTH_STATUS_ERROR: str = "error"
# --- HTTP Status Messages ---
HTTP_400_DETAIL: str = "Bad Request"
HTTP_401_DETAIL: str = "Unauthorized"
HTTP_403_DETAIL: str = "Forbidden"
HTTP_404_DETAIL: str = "Not Found"
HTTP_422_DETAIL: str = "Unprocessable Entity"
HTTP_429_DETAIL: str = "Too Many Requests"
HTTP_500_DETAIL: str = "Internal Server Error"
HTTP_503_DETAIL: str = "Service Unavailable"
# --- Database Error Messages ---
DB_CONNECTION_ERROR: str = "Database connection error"
DB_INTEGRITY_ERROR: str = "Database integrity error"
DB_TRANSACTION_ERROR: str = "Database transaction error"
DB_QUERY_ERROR: str = "Database query error"
# --- Auth Error Messages ---
AUTH_INVALID_CREDENTIALS: str = "Invalid username or password"
AUTH_NOT_AUTHENTICATED: str = "Not authenticated"
AUTH_JWT_ERROR: str = "JWT token error: {error}"
AUTH_JWT_UNEXPECTED_ERROR: str = "Unexpected JWT error: {error}"
AUTH_HEADER_NAME: str = "WWW-Authenticate"
AUTH_HEADER_PREFIX: str = "Bearer"
# OAuth Settings
# IMPORTANT: For Google OAuth to work, you MUST set the following environment variables
# (e.g., in your .env file):
# GOOGLE_CLIENT_ID: Your Google Cloud project's OAuth 2.0 Client ID
# GOOGLE_CLIENT_SECRET: Your Google Cloud project's OAuth 2.0 Client Secret
# Ensure the GOOGLE_REDIRECT_URI below matches the one configured in your Google Cloud Console.
GOOGLE_CLIENT_ID: str = ""
GOOGLE_CLIENT_SECRET: str = ""
GOOGLE_REDIRECT_URI: str = "http://localhost:8000/api/v1/auth/google/callback"
APPLE_CLIENT_ID: str = ""
APPLE_TEAM_ID: str = ""
APPLE_KEY_ID: str = ""
APPLE_PRIVATE_KEY: str = ""
APPLE_REDIRECT_URI: str = "http://localhost:8000/api/v1/auth/apple/callback"
# Session Settings
SESSION_SECRET_KEY: str = "your-session-secret-key" # Change this in production
ACCESS_TOKEN_EXPIRE_MINUTES: int = 480 # 8 hours instead of 30 minutes
# Redis Settings
REDIS_URL: str = "redis://localhost:6379"
REDIS_PASSWORD: str = ""
class Config:
env_file = ".env"
env_file_encoding = 'utf-8'
extra = "ignore"
@property
def cors_origins_list(self) -> List[str]:
"""Convert CORS_ORIGINS string to list"""
return [origin.strip() for origin in self.CORS_ORIGINS.split(",")]
@property
def is_production(self) -> bool:
"""Check if running in production environment"""
return self.ENVIRONMENT.lower() == "production"
@property
def is_development(self) -> bool:
"""Check if running in development environment"""
return self.ENVIRONMENT.lower() == "development"
@property
def docs_url(self) -> str | None:
"""Return docs URL only in development"""
return self.API_DOCS_URL if self.is_development else None
@property
def redoc_url(self) -> str | None:
"""Return redoc URL only in development"""
return self.API_REDOC_URL if self.is_development else None
@property
def openapi_url(self) -> str | None:
"""Return OpenAPI URL only in development"""
return self.API_OPENAPI_URL if self.is_development else None
settings = Settings()
# Validation for critical settings
if settings.DATABASE_URL is None:
raise ValueError("DATABASE_URL environment variable must be set.")
# Enforce secure secret key
if not settings.SECRET_KEY:
raise ValueError("SECRET_KEY environment variable must be set. Generate a secure key using: openssl rand -hex 32")
# Validate secret key strength
if len(settings.SECRET_KEY) < 32:
raise ValueError("SECRET_KEY must be at least 32 characters long for security")
# Production-specific validations
if settings.is_production:
if settings.SESSION_SECRET_KEY == "your-session-secret-key":
raise ValueError("SESSION_SECRET_KEY must be changed from default value in production")
if not settings.SENTRY_DSN:
logger.warning("SENTRY_DSN not set in production environment. Error tracking will be unavailable.")
if settings.GEMINI_API_KEY is None:
logger.error("CRITICAL: GEMINI_API_KEY environment variable not set. Gemini features will be unavailable.")
else:
# Optional: Log partial key for confirmation (avoid logging full key)
logger.info(f"GEMINI_API_KEY loaded (starts with: {settings.GEMINI_API_KEY[:4]}...).")
# Log environment information
logger.info(f"Application starting in {settings.ENVIRONMENT} environment")
if settings.is_production:
logger.info("Production mode: API documentation disabled")
else:
logger.info(f"Development mode: API documentation available at {settings.API_DOCS_URL}")