Spaces:
Sleeping
Sleeping
| """Environment-loaded configuration. | |
| All environment variables used anywhere in the backend live here so | |
| the routers / services never call ``os.environ.get`` directly. | |
| """ | |
| # Make sure .env is loaded no matter who imports us first. | |
| from app._env import load_app_env # noqa: E402 | |
| load_app_env() | |
| import os | |
| # --------------------------------------------------------------------------- # | |
| # Supabase # | |
| # --------------------------------------------------------------------------- # | |
| SUPABASE_URL = os.environ.get("SUPABASE_URL", "") | |
| SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY", "") | |
| SUPABASE_JWT_SECRET = os.environ.get("SUPABASE_JWT_SECRET", "") | |
| # --------------------------------------------------------------------------- # | |
| # AI providers β endpoints, model names, keys # | |
| # --------------------------------------------------------------------------- # | |
| # Cloudflare Workers AI β secondary tier in the fallback chain. | |
| CLOUDFLARE_API_TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN", "") | |
| CLOUDFLARE_ACCOUNT_ID = os.environ.get("CLOUDFLARE_ACCOUNT_ID", "") | |
| CLOUDFLARE_MODEL = os.environ.get("CLOUDFLARE_MODEL", "@cf/meta/llama-3.1-8b-instruct") | |
| CLOUDFLARE_URL = ( | |
| f"https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/run/{CLOUDFLARE_MODEL}" | |
| if CLOUDFLARE_ACCOUNT_ID else "" | |
| ) | |
| # Groq β secondary AI, also the legacy scenario/eval backend. | |
| GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") | |
| GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions" | |
| GROQ_MODEL = os.environ.get("GROQ_MODEL", "llama-3.1-8b-instant") | |
| # NVIDIA β tertiary AI tier. | |
| NVIDIA_API_KEY = os.environ.get("NVIDIA_API_KEY", "") | |
| NVIDIA_MODEL = os.environ.get("NVIDIA_MODEL", "deepseek-ai/deepseek-v4-pro") | |
| NVIDIA_URL = "https://integrate.api.nvidia.com/v1/chat/completions" | |
| # Mistral β primary AI, runs first per user request. | |
| MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "") | |
| MISTRAL_MODEL = os.environ.get("MISTRAL_MODEL", "mistral-large-latest") | |
| MISTRAL_API_URL = os.environ.get( | |
| "MISTRAL_API_URL", | |
| "https://api.mistral.ai/v1/chat/completions", | |
| ) | |
| # Supabase Edge Functions used as auth / XP proxies. | |
| SUPABASE_EDGE_URL = f"{SUPABASE_URL}/functions/v1" | |
| # --------------------------------------------------------------------------- # | |
| # Supabase Storage bucket for AI-generated challenge files # | |
| # --------------------------------------------------------------------------- # | |
| # Name of the public bucket ``app.services.file_storage`` uploads | |
| # ``fileToGenerate`` payloads to. The bucket's RLS policies live in | |
| # ``db/schema/010_challenge_files_bucket.sql``. | |
| CHALLENGE_FILES_BUCKET = "challenge-files" | |
| # --------------------------------------------------------------------------- # | |
| # Per-table canonical module (see AGENTS.md "Challenge Type vs Module") # | |
| # --------------------------------------------------------------------------- # | |
| # One entry per per-type challenges table β the ONLY value the | |
| # ``module`` column is allowed to take for rows going into that | |
| # table. ``normalize_row_module()`` enforces this at insert time so | |
| # AI generators can keep using topic-shaped names internally | |
| # (``xss``, ``sqli``, ``hash-cracking``) without violating the | |
| # post-011 CHECK constraints. | |
| TABLE_CANONICAL_MODULE: dict[str, str] = { | |
| "encryption_challenges": "crypto", | |
| "code_fixing_challenges": "code-fixing", | |
| "log_analysis_challenges": "log-analysis", | |
| "vulnerability_hunter_challenges": "vulnerability-hunter", | |
| "steganography_challenges": "steganography", | |
| "web_exploitation_challenges": "web-exploitation", | |
| } | |
| def normalize_row_module(table: str, row: dict) -> dict: | |
| """Enforce the canonical ``module`` value for a row going to ``table``. | |
| The migration 011 CHECK constraint rejects any row whose | |
| ``module`` column does not equal the canonical challenge type | |
| for its table. The AI generators used to populate ``module`` | |
| with a topic-shaped string (``xss``, ``hash-cracking``, | |
| ``web-security``, β¦) β we transparently rewrite that into: | |
| * ``module`` = canonical challenge type (so the DB accepts it) | |
| * ``topic`` = the original topic-shaped string (so the front-end | |
| can still show / filter on it) | |
| If the row already carries the canonical value, ``topic`` falls | |
| back to whatever ``module`` said. The function returns the | |
| mutated ``row`` (in place) for convenience; the return value is | |
| the same dict. | |
| Raises ``ValueError`` when called with an unknown table name β | |
| that always indicates a programming error, never a bad row. | |
| """ | |
| canonical = TABLE_CANONICAL_MODULE.get(table) | |
| if canonical is None: | |
| raise ValueError( | |
| f"normalize_row_module: unknown table {table!r}. " | |
| f"Add it to app.core.config.TABLE_CANONICAL_MODULE." | |
| ) | |
| original = row.get("module", "") | |
| # Move the original topic-shaped value into ``topic`` (if not | |
| # already set) and pin ``module`` to the canonical challenge type. | |
| if row.get("topic") is None or row.get("topic") == "": | |
| row["topic"] = original or canonical | |
| row["module"] = canonical | |
| return row | |