Files
la-voix-du-peuple/scripts/dev-local.sh
T
billisdead defc07648c Security: close the 9 items from the June review + one found along the way
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.
2026-09-10 09:27:50 +02:00

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"