agilentics / boiler
"""Configuration container for environment variables.

Two rules this file exists to enforce, both of which are easy to lose:

  * Nothing secret has a hardcoded default. An unset DB_PASS fails loudly at
    connect time rather than silently using a password baked into the repo.

  * An unset feature variable means "that feature is off", not "crash". The app
    must run end-to-end - locally and in CI - with no accounts registered at any
    third party. SMTP_HOST below is the worked example; copy the stance when you
    add Stripe, an OAuth client, or anything else with a credential.

Read at import time, so load_dotenv() has to run before the class body.
"""
import os
from typing import Optional

from dotenv import load_dotenv

# Existing environment variables win, so App Engine's injected config is never
# overwritten by a stray .env on a developer's machine.
load_dotenv(override=False)


class Config:
    # "prod" means "deployed behind HTTPS", not "the production environment":
    # it is what sets SESSION_COOKIE_SECURE and arms the weak-SECRET_KEY guard
    # in main.py. The integration deployment sets it too.
    ENV: str = os.getenv("ENV", "local")

    # The organisation manage.py targets when no --org is given.
    DEFAULT_ORG_SLUG: str = os.getenv("DEFAULT_ORG_SLUG", "myapp")

    # --- database ---------------------------------------------------------
    # Local dev / CI:  TCP to localhost (Cloud SQL Auth Proxy or local Postgres)
    # App Engine:      Unix socket, set via app.yaml
    DB_USER: Optional[str] = os.getenv("DB_USER", "myapp")
    DB_PASS: Optional[str] = os.getenv("DB_PASS", "")
    DB_NAME: Optional[str] = os.getenv("DB_NAME", "myapp")
    DB_PORT: str = os.getenv("DB_PORT", "5432")
    INSTANCE_UNIX_SOCKET: Optional[str] = os.getenv("INSTANCE_UNIX_SOCKET", "")

    # Signs the session cookie, which is the credential that says a request is
    # authenticated. main.py refuses to start in prod if this is still a
    # placeholder.
    SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-only-not-for-production")

    # The scheme+host that security-sensitive external links (password reset,
    # email verification, a payment provider's return URL) must be built from.
    # Left blank, endpoints/crud.public_base_url() derives it from the request -
    # but only for loopback hosts, so a deployment cannot have its links poisoned
    # through the attacker-controllable Host header.
    PUBLIC_BASE_URL: str = os.getenv("PUBLIC_BASE_URL", "")

    # --- outbound email ---------------------------------------------------
    # The worked example of "unset config = feature off". SMTP_HOST unset means
    # "no mail server": send_mail logs the message instead of sending, so local
    # dev and CI run any flow that mails without a live server.
    #   STARTTLS  upgrade a plaintext connection to TLS (the usual port 587)
    #   SSL       implicit TLS from the first byte (port 465); overrides STARTTLS
    SMTP_HOST: str = os.getenv("SMTP_HOST", "")
    SMTP_PORT: str = os.getenv("SMTP_PORT", "587")
    SMTP_USER: str = os.getenv("SMTP_USER", "")
    SMTP_PASS: str = os.getenv("SMTP_PASS", "")
    SMTP_FROM: str = os.getenv("SMTP_FROM", "")
    SMTP_STARTTLS: bool = os.getenv("SMTP_STARTTLS", "true").lower() == "true"
    SMTP_SSL: bool = os.getenv("SMTP_SSL", "false").lower() == "true"

    # --- Google sign-in (OAuth 2.0 / OIDC) --------------------------------
    # Both unset means "Sign in with Google" is off: the button is hidden and
    # the /auth/google routes bounce to /login - the same stance SMTP_HOST takes.
    # Register an OAuth client in the Google console with the redirect URI
    # <PUBLIC_BASE_URL>/auth/google/callback.
    GOOGLE_CLIENT_ID: str = os.getenv("GOOGLE_CLIENT_ID", "").strip()
    GOOGLE_CLIENT_SECRET: str = os.getenv("GOOGLE_CLIENT_SECRET", "").strip()

    # --- your application's config goes below ------------------------------
    # Follow the split the two files above draw:
    #   a host, an id, a public address, a flag  -> app.yaml (committed)
    #   anything that authenticates              -> env_secrets.yaml (gitignored,
    #                                               rendered by CI)
    # and .strip() every secret, so a trailing newline from a piped value is not
    # baked into a comparison.