diff --git a/.env.example b/.env.example index 0fd42e4..778c39f 100644 --- a/.env.example +++ b/.env.example @@ -41,6 +41,14 @@ COOKIE_DOMAIN= # Cookie max age in seconds (default: 3600 = 1 hour) COOKIE_MAX_AGE=3600 +# ------------------------------------------- +# Reverse Proxy / TLS Termination +# ------------------------------------------- +# Enable to trust X-Forwarded-* headers (set true behind ingress/nginx) +PROXY_HEADERS=false +# Comma-separated trusted proxy hosts/IPs (use "*" to trust all) +TRUSTED_PROXY_HOSTS=127.0.0.1,::1 + # ------------------------------------------- # Database Configuration (PostgreSQL) # ------------------------------------------- diff --git a/backend/src/config.py b/backend/src/config.py index 35a8bfb..af9d7d0 100644 --- a/backend/src/config.py +++ b/backend/src/config.py @@ -25,6 +25,12 @@ class Settings(BaseSettings): cookie_max_age: int = Field(default=28800, env="COOKIE_MAX_AGE") # 8 hours cookie_name: str = Field(default="access_token", env="COOKIE_NAME") + # Proxy / TLS termination settings + # Enable to honor X-Forwarded-Proto when behind a reverse proxy (ingress/nginx) + proxy_headers: bool = Field(default=False, env="PROXY_HEADERS") + # Comma-separated list of trusted proxy hosts/IPs. Use "*" to trust all. + trusted_proxy_hosts: str = Field(default="127.0.0.1,::1", env="TRUSTED_PROXY_HOSTS") + # Database settings database_host: str = Field(default="postgres", env="DATABASE_HOST") database_port: int = Field(default=5432, env="DATABASE_PORT") diff --git a/backend/src/main.py b/backend/src/main.py index e3613aa..0286177 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -2,6 +2,7 @@ from contextlib import asynccontextmanager from typing import List, Optional from fastapi import FastAPI, Depends, Request, HTTPException, status from fastapi.middleware.cors import CORSMiddleware +from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.responses import RedirectResponse from sqlalchemy.ext.asyncio import AsyncSession @@ -139,6 +140,15 @@ app = FastAPI( lifespan=lifespan ) +# Respect X-Forwarded-Proto/For headers when behind a reverse proxy +if settings.proxy_headers: + trusted_hosts = [ + host.strip() + for host in settings.trusted_proxy_hosts.split(",") + if host.strip() + ] + app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=trusted_hosts) + # Configure CORS app.add_middleware( CORSMiddleware, diff --git a/frontend/src/components/LanguageSelector.tsx b/frontend/src/components/LanguageSelector.tsx index 42fab85..17de897 100644 --- a/frontend/src/components/LanguageSelector.tsx +++ b/frontend/src/components/LanguageSelector.tsx @@ -15,6 +15,12 @@ interface LanguageSelectorProps { export default function LanguageSelector({ className = '', compact = false }: LanguageSelectorProps) { const { i18n, t } = useTranslation('common') + const resolvedLanguage = i18n.resolvedLanguage || i18n.language + const normalizedLanguage = (resolvedLanguage?.split('-')[0] || 'en') as LanguageCode + const selectedLanguage = LANGUAGES.some((lang) => lang.code === normalizedLanguage) + ? normalizedLanguage + : 'en' + const handleLanguageChange = (e: React.ChangeEvent) => { const newLang = e.target.value as LanguageCode i18n.changeLanguage(newLang) @@ -23,7 +29,7 @@ export default function LanguageSelector({ className = '', compact = false }: La return (
setNewProjectName(e.target.value)} - placeholder={t('createProject.projectNamePlaceholder')} - className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent" - required - /> -
- - {/* Project Description */} -
- -