Spaces:
Sleeping
Sleeping
File size: 5,301 Bytes
80a4a65 082c217 80a4a65 bedbcad 80a4a65 082c217 80a4a65 082c217 80a4a65 082c217 80a4a65 082c217 80a4a65 4871da9 4c41470 80a4a65 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | """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
|