Findings from the 2026-06-24 security review, now mandatory before this platform leaves the LAN for a public VPS (see budget/externalization note): - Timing attack on admin password: hmac.compare_digest instead of != - CORS wildcard removed, restricted to ALLOWED_ORIGINS (needed for credentials) - hCaptcha now fails closed on network error instead of open - HMAC signatures use the full SHA-256 digest instead of a 64-bit truncation - Consultation-scoped submissions were missing the consent/cooldown checks the global submission route already had - Admin search capped at 200 chars (ILIKE cost) - Webhook URLs validated against SSRF (private/loopback/link-local/metadata IPs rejected, re-checked at trigger time too, redirects not followed) - Flood tracker backed by Redis when REDIS_URL is set (shared across Gunicorn workers, survives restarts), falls back to in-memory otherwise - Admin session moved from sessionStorage/Bearer token to an httpOnly+Secure+ SameSite=Strict cookie; new /api/admin/logout and /api/admin/session routes Also found while wiring ProxyFix for the cookie's Secure flag: the app never told Flask about the Nginx reverse proxy, so request.remote_addr saw Nginx's own IP for every request in production — silently breaking rate limiting, flood detection, and IP blacklist (everyone would collapse onto one shared key). Added ProxyFix (1 hop). CSV exports now stream instead of loading everything into memory. AI prompts delimit citizen-submitted content explicitly and instruct the model to treat it as inert data, never instructions, to reduce prompt injection surface. Docs (DEX.md, SECURITE_ANTI_ABUS.md, README.md, DEPLOIEMENT.md) and dev-local.sh updated to match.
171 lines
7.6 KiB
Bash
Executable File
171 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# dev-local.sh — Lance l'API Flask + le serveur Vite en mode développement
|
|
#
|
|
# Usage : bash scripts/dev-local.sh
|
|
#
|
|
# Le script installe automatiquement les prérequis manquants (venv Python,
|
|
# dépendances pip, pnpm, node_modules). Node.js et PostgreSQL doivent être
|
|
# présents au préalable (installation système, voir docs/INSTALL_ROCKY.md).
|
|
#
|
|
# Pour accéder à l'interface depuis votre machine locale, ouvrez un tunnel SSH :
|
|
# ssh -L 5173:localhost:5173 -L 8080:localhost:8080 utilisateur@votre-serveur
|
|
# Puis ouvrez http://localhost:5173 dans votre navigateur.
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
ENV_FILE="$ROOT/.env"
|
|
|
|
# ─── Fichier .env ─────────────────────────────────────────────────────────────
|
|
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo "Erreur : fichier .env introuvable."
|
|
echo "Copiez .env.example et remplissez les valeurs :"
|
|
echo " cp \"$ROOT/.env.example\" \"$ENV_FILE\" && \${EDITOR:-vim} \"$ENV_FILE\""
|
|
exit 1
|
|
fi
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
set +a
|
|
|
|
FLASK_PORT="${PORT:-8080}"
|
|
VITE_PORT=5173
|
|
|
|
# ─── Python : venv + dépendances pip ─────────────────────────────────────────
|
|
|
|
if [ ! -f "$ROOT/.venv/bin/python" ]; then
|
|
echo "→ Création du virtualenv Python…"
|
|
|
|
# Cherche python3.11, puis python3, puis python (>= 3.9)
|
|
PYTHON_BIN=""
|
|
for bin in python3.11 python3 python; do
|
|
if command -v "$bin" &>/dev/null \
|
|
&& "$bin" -c "import sys; sys.exit(0 if sys.version_info >= (3, 9) else 1)" 2>/dev/null; then
|
|
PYTHON_BIN="$bin"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$PYTHON_BIN" ]; then
|
|
echo "Erreur : Python 3.9+ introuvable."
|
|
echo "Installez Python 3.11 :"
|
|
echo " Rocky/RHEL → dnf install -y python3.11"
|
|
echo " Debian/Ubuntu → apt install -y python3.11"
|
|
exit 1
|
|
fi
|
|
|
|
"$PYTHON_BIN" -m venv "$ROOT/.venv"
|
|
echo " Virtualenv créé avec $("$ROOT/.venv/bin/python" --version)."
|
|
fi
|
|
|
|
echo "→ Vérification des dépendances Python…"
|
|
"$ROOT/.venv/bin/pip" install --upgrade pip --quiet
|
|
"$ROOT/.venv/bin/pip" install -r "$ROOT/artifacts/flask-api/requirements.txt" --quiet
|
|
echo " OK."
|
|
|
|
# ─── Node.js ──────────────────────────────────────────────────────────────────
|
|
|
|
if ! command -v node &>/dev/null; then
|
|
echo "Erreur : Node.js introuvable (installation système requise)."
|
|
echo "Installez Node.js 20 :"
|
|
echo " Rocky/RHEL → curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -"
|
|
echo " dnf install -y nodejs"
|
|
echo " Debian/Ubuntu → curl -fsSL https://deb.nodesource.com/setup_20.x | bash -"
|
|
echo " apt install -y nodejs"
|
|
echo " nvm (universel) → https://github.com/nvm-sh/nvm"
|
|
exit 1
|
|
fi
|
|
|
|
# ─── pnpm ─────────────────────────────────────────────────────────────────────
|
|
|
|
if ! command -v pnpm &>/dev/null; then
|
|
echo "→ Installation de pnpm…"
|
|
if command -v corepack &>/dev/null; then
|
|
corepack enable
|
|
corepack prepare pnpm@latest --activate
|
|
elif command -v npm &>/dev/null; then
|
|
npm install -g pnpm --quiet
|
|
else
|
|
echo "Erreur : ni corepack ni npm disponibles pour installer pnpm."
|
|
exit 1
|
|
fi
|
|
echo " pnpm $(pnpm --version) installé."
|
|
fi
|
|
|
|
# ─── Dépendances Node ─────────────────────────────────────────────────────────
|
|
|
|
if [ ! -d "$ROOT/node_modules" ]; then
|
|
echo "→ Installation des dépendances Node…"
|
|
(cd "$ROOT" && pnpm install --frozen-lockfile)
|
|
fi
|
|
|
|
# ─── Bannière ─────────────────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "┌──────────────────────────────────────────────────────────────┐"
|
|
echo "│ La Voix du Peuple — développement local │"
|
|
echo "├──────────────────────────────────────────────────────────────┤"
|
|
printf "│ API Flask → http://localhost:%-5s │\n" "${FLASK_PORT}"
|
|
printf "│ Interface → http://localhost:%-5s │\n" "${VITE_PORT}"
|
|
echo "├──────────────────────────────────────────────────────────────┤"
|
|
echo "│ Tunnel SSH depuis votre machine locale : │"
|
|
printf "│ ssh -L %s:localhost:%s \\\\\n│ -L %s:localhost:%s utilisateur@serveur │\n" \
|
|
"${VITE_PORT}" "${VITE_PORT}" "${FLASK_PORT}" "${FLASK_PORT}"
|
|
printf "│ Puis ouvrez : http://localhost:%-5s │\n" "${VITE_PORT}"
|
|
echo "└──────────────────────────────────────────────────────────────┘"
|
|
echo ""
|
|
|
|
# ─── Nettoyage à la sortie ────────────────────────────────────────────────────
|
|
|
|
cleanup() {
|
|
echo ""
|
|
echo "Arrêt des serveurs…"
|
|
kill "$FLASK_PID" "$VITE_PID" 2>/dev/null || true
|
|
wait "$FLASK_PID" "$VITE_PID" 2>/dev/null || true
|
|
echo "Arrêté."
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# ─── Démarrage de Flask ───────────────────────────────────────────────────────
|
|
|
|
echo "→ Démarrage de Flask (port ${FLASK_PORT})…"
|
|
(
|
|
cd "$ROOT/artifacts/flask-api"
|
|
# CORS n'accepte plus de wildcard (nécessaire pour le cookie de session admin) —
|
|
# sans ça, le Vite local ne peut plus parler à Flask en dev cross-origin.
|
|
# Respecte un ALLOWED_ORIGINS déjà défini dans .env, sinon retombe sur l'origine Vite locale.
|
|
PORT="${FLASK_PORT}" ALLOWED_ORIGINS="${ALLOWED_ORIGINS:-http://localhost:${VITE_PORT}}" \
|
|
"$ROOT/.venv/bin/python" app.py
|
|
) &
|
|
FLASK_PID=$!
|
|
|
|
# Laisser Flask s'initialiser (init_db inclus)
|
|
sleep 2
|
|
|
|
if ! kill -0 "$FLASK_PID" 2>/dev/null; then
|
|
echo ""
|
|
echo "Erreur : Flask ne démarre pas."
|
|
echo "Vérifiez DATABASE_URL et les autres variables dans .env."
|
|
echo "Logs disponibles dans le terminal ci-dessus."
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Démarrage du serveur Vite ────────────────────────────────────────────────
|
|
|
|
echo "→ Démarrage du serveur Vite (port ${VITE_PORT})…"
|
|
(
|
|
cd "$ROOT/artifacts/voix-du-peuple"
|
|
# PORT non défini → Vite retombe sur 5173 (voir vite.config.ts)
|
|
# VITE_API_URL pointe vers Flask local, accessible via le tunnel SSH
|
|
unset PORT
|
|
VITE_API_URL="http://localhost:${FLASK_PORT}" pnpm dev
|
|
) &
|
|
VITE_PID=$!
|
|
|
|
echo ""
|
|
echo "Les deux serveurs sont démarrés. Appuyez sur Ctrl+C pour arrêter."
|
|
echo ""
|
|
|
|
# Attendre la fin de l'un ou l'autre des processus
|
|
wait -n "$FLASK_PID" "$VITE_PID" 2>/dev/null || wait "$FLASK_PID" "$VITE_PID"
|