diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d520e6f --- /dev/null +++ b/.env.example @@ -0,0 +1,26 @@ +# WealthySmart environment template. +# Copy to .env for local development. Production values live in Gitea Actions +# secrets and are written to .env.prod by .github/workflows/deploy.yml on every +# deploy — new variables must be added BOTH there and here. + +# ── Database (dev compose; must match the wealthysmart-db-dev volume) ──────── +POSTGRES_USER=wealthy_user +POSTGRES_PASSWORD=wealthy_pass +POSTGRES_DB=wealthysmart + +# ── Auth (REQUIRED — the backend refuses to boot without these) ────────────── +# Generate with: openssl rand -hex 32 +SECRET_KEY= +ADMIN_USERNAME=admin +# Must not be "admin". +ADMIN_PASSWORD= +# Cookies require HTTPS when true. Set false for local HTTP development. +COOKIE_SECURE=false + +# ── Push notifications (optional in dev) ───────────────────────────────────── +VAPID_PRIVATE_KEY= +VAPID_PUBLIC_KEY= + +# ── AI agent (optional in dev; Asistente won't work without it) ────────────── +OPENAI_API_KEY= +AGENT_MODEL=gpt-5.4-mini diff --git a/CLAUDE.md b/CLAUDE.md index 660d65d..8abe827 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,6 +13,10 @@ Personal finance management web app. cd frontend && pnpm install && pnpm run dev ``` +The backend refuses to boot without `SECRET_KEY`, `ADMIN_USERNAME`, and +`ADMIN_PASSWORD` (no insecure defaults). Copy `.env.example` to `.env` and fill +it in; the dev docker-compose reads it. + ## Local Docker ```bash diff --git a/backend/app/config.py b/backend/app/config.py index c2105a9..1270d0e 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -1,12 +1,19 @@ +from pydantic import model_validator from pydantic_settings import BaseSettings class Settings(BaseSettings): DATABASE_URL: str = "postgresql://wealthy_user:wealthy_pass@db:5432/wealthysmart" - SECRET_KEY: str = "change-me-in-production" - ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 30 # 30 days - ADMIN_USERNAME: str = "admin" - ADMIN_PASSWORD: str = "admin" + # No defaults: the app must refuse to boot without real values (see _reject_weak_secrets). + SECRET_KEY: str + ADMIN_USERNAME: str + ADMIN_PASSWORD: str + ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days + COOKIE_SECURE: bool = True # dev compose sets "false"; prod serves over TLS + CORS_ORIGINS: str = ( + "https://wealth.cescalante.dev," + "http://localhost:3000,http://localhost:3001,http://localhost:5175" + ) BCCR_API_EMAIL: str = "" BCCR_API_TOKEN: str = "" VAPID_PRIVATE_KEY: str = "" @@ -15,6 +22,23 @@ class Settings(BaseSettings): OPENAI_API_KEY: str = "" AGENT_MODEL: str = "gpt-5.4-mini" + @property + def cors_origins_list(self) -> list[str]: + return [o.strip() for o in self.CORS_ORIGINS.split(",") if o.strip()] + + @model_validator(mode="after") + def _reject_weak_secrets(self) -> "Settings": + if len(self.SECRET_KEY) < 32 or self.SECRET_KEY == "change-me-in-production": + raise ValueError( + "SECRET_KEY must be a random value of at least 32 characters " + "(generate one with: openssl rand -hex 32)" + ) + if not self.ADMIN_USERNAME: + raise ValueError("ADMIN_USERNAME must be set") + if not self.ADMIN_PASSWORD or self.ADMIN_PASSWORD == "admin": + raise ValueError("ADMIN_PASSWORD must be set and must not be 'admin'") + return self + class Config: env_file = ".env" diff --git a/docker-compose.yml b/docker-compose.yml index 28ac1b6..0a6a3c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,15 +3,15 @@ services: image: postgres:16-alpine container_name: wealthysmart-db-dev environment: - POSTGRES_USER: wealthy_user - POSTGRES_PASSWORD: wealthy_pass - POSTGRES_DB: wealthysmart + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env} ports: - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U wealthy_user -d wealthysmart"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 3s retries: 5 @@ -22,7 +22,11 @@ services: dockerfile: Dockerfile container_name: wealthysmart-backend-dev environment: - DATABASE_URL: postgresql://wealthy_user:wealthy_pass@db:5432/wealthysmart + DATABASE_URL: postgresql://${POSTGRES_USER:?}:${POSTGRES_PASSWORD:?}@db:5432/${POSTGRES_DB:?} + SECRET_KEY: ${SECRET_KEY:?set SECRET_KEY in .env (openssl rand -hex 32)} + ADMIN_USERNAME: ${ADMIN_USERNAME:?set ADMIN_USERNAME in .env} + ADMIN_PASSWORD: ${ADMIN_PASSWORD:?set ADMIN_PASSWORD in .env} + COOKIE_SECURE: "false" VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} OPENAI_API_KEY: ${OPENAI_API_KEY:-}