Commit Graph
13 Commits
Author SHA1 Message Date
billisdeadandClaude Sonnet 5.5 13677b3ce3 Add admin AI models panel (list, test, select Mistral models)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 09:55:37 +02:00
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
billisdeadandClaude Sonnet 4.6 41556d3ea2 Improve synthesis display, PDF exports, and priority documentation
- home.tsx: add SynthesisText component — detects "Sur X : / Concernant X :"
  thematic prefixes and renders them in serif/teal, replaces raw whitespace-pre-line
- home.tsx: redesign client-side PDF — site color palette (#F9F7F1, #1b5f6a),
  Georgia serif typography, thematic prefix highlighting, tricolor stripe
- app.py: redesign consultation print export — same visual identity, server-side
  paragraph parsing with theme span injection before HTML-escaping
- transparence.tsx: rewrite priority explanation to be explicit about what
  "critique/haute/moyenne" means in practice (injection vs monitoring, why)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 10:05:28 +02:00
billisdeadandClaude Sonnet 4.6 2ef14ac765 Add mobile overflow nav, law check feature, and favicon
Nav: "Fonctionnement" and "Flyer QR" restored on mobile via a ···
overflow dropdown (click-outside closes); other links always visible

Law check: new POST /api/check-law endpoint (5/min, 20/h) calls
mistral-small with a new LAW_CHECK_PROMPT to detect if a proposal is
already covered by French or EU law. Informational only, non-blocking.
Frontend: "Vérifier le cadre légal existant" button below textarea in
home.tsx; result displayed with Scale icon; resets on content change.

Favicon: replaced placeholder red square with a petrol rounded square
containing a serif "V" (for Voix) in warm cream — matches brand palette

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 17:57:29 +02:00
billisdeadandClaude Sonnet 4.6 5c5152a387 Add automatic IP blacklisting after 2 illegal contributions
Persistence: PostgreSQL table ip_abuse (ip_hash SHA-256, rejection_count,
timestamps, last_idea_id, blacklisted_at, expires_at). No raw IP stored.

Logic:
- First illegal contribution: recorded, tolerated (benefit of the doubt)
- Second illegal contribution: 30-day block (IP_BLACKLIST_DAYS, configurable)
- Counter is cumulative — valid contributions do not reset it
- Block check fires before all other validations in both submit routes

Backend:
- database.py: check_ip_blacklist, record_ip_rejection, get_ip_blacklist,
  remove_ip_blacklist; ip_abuse table created in init_db()
- app.py: _get_ip_hash() helper; blacklist check + record in submit_idea()
  and submit_consultation_idea(); admin routes GET/DELETE /api/admin/ip-blacklist

Admin panel: new "Blacklist" tab showing active entries with hash, rejection
count, trigger idea id, dates; "Lever" button for manual removal

Docs:
- SECURITE_ANTI_ABUS.md: section 8 describing the mechanism, RGPD basis,
  limits, and IP_BLACKLIST_DAYS env var
- privacy-policy.tsx: IP row updated to reflect hash storage + legal basis

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 17:42:31 +02:00
billisdeadandClaude Sonnet 4.6 9553115d8c Fix 10 bugs found in general code review
Backend (app.py / database.py / ai_agent.py):
- [Critique] Autoclose loop: add pg_try_advisory_lock so only one Gunicorn
  worker runs the check per 60s cycle; add random startup jitter
- [Critique] admin_delete_idea: pass consultation_id to
  _update_synthesis_background so the right synthesis is regenerated
- [Majeur] admin_login: return HMAC-signed session token instead of raw
  ADMIN_SECRET; require_admin verifies the signature (TTL 8h)
- [Majeur] bulk_delete: replace str.isdigit() (Unicode-unsafe) with
  try/except int() to prevent crash on Unicode digit characters
- [Majeur] create_consultation: force UTC timezone on naive datetime from
  fromisoformat() to prevent TypeError when comparing with UTC-aware now()
- [Majeur] ai_agent.py: fix 'raw' in dir() -> 'raw' in locals() so the
  JSON parse error log actually shows the raw response
- [Mineur] export print: use datetime.now(UTC) instead of datetime.now()

Frontend (React):
- [Majeur] consultation.tsx: show startsAt (not endsAt) for upcoming
  consultations; add startsAt variable
- [Majeur] consultations-list.tsx: same fix for the list view
- [Mineur] home.tsx: guard new Date(idea.createdAt) against null
- [Mineur] admin.tsx: check HTTP status in exportCsv XHR before creating
  download link; show error toast on non-200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 16:26:52 +02:00
billisdeadandClaude Sonnet 4.6 e31c268d54 Enforce consent token on submission, reduce default cooldown
- Require signed _ct cookie (set by POST /api/consent) before accepting any
  submission. Direct API calls without a prior consent flow are rejected 403.
- Add _sign_consent_token / _verify_consent_token (HMAC-SHA256, 24h TTL).
- Lower default CONTRIBUTION_COOLDOWN_SECONDS from 3600 to 240 (4 min).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 16:08:11 +02:00
billisdeadandClaude Sonnet 4.6 1d180acf15 Fix init_db not called under Gunicorn
init_db() was only called inside if __name__ == "__main__", which Gunicorn
never executes. Tables were never created, causing every request to fail.
Move the call to module level so it runs on import, before the autoclose
thread starts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 15:57:52 +02:00
billisdeadandClaude Sonnet 4.6 fbc1fad8b9 P5 — Mode consultation ciblée (Option B, implémentation complète)
Backend :
- Nouvelle table `consultations` (slug unique, fenêtre temporelle, webhook, logo)
- `ideas.consultation_id` FK nullable (NULL = contexte global home)
- `synthesis.consultation_id` FK nullable (synthèse par contexte)
- Boucle auto-fermeture (thread daemon, 60 s) — ferme + webhook à l'échéance
- Webhook de clôture : POST JSON (synthèse + métadonnées) via urllib.request
- Routes publiques : GET/POST /api/consultations/<slug>, synthèse, contributions, export/print
- Routes admin : list, create, close (+ webhook), delete (cascade explicite)
- CSP ajustée sur /export/print pour autoriser window.print()

Frontend :
- Nouvelle page /consultation/:slug — formulaire, synthèse live, contributions paginées, PDF
- Admin panel : onglet Consultations — liste, formulaire création, fermeture, suppression

Docs : DAT.md v1.5, DEX.md v1.7 (section P5, tables, routes, webhook)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 10:00:39 +02:00
billisdeadandClaude Sonnet 4.6 a7b7684e87 Conformité RGPD (P3) + transparence éditoriale (P4)
P3 — RGPD :
- Table `consents` + `POST /api/consent` (art. 7.1 — preuve du consentement)
- Dialogue de consentement explicite avant la première contribution (art. 9.2.a)
- Pages `/mentions-legales` et `/politique-confidentialite`
- `docs/RGPD.md` — registre des traitements, bases légales, sous-traitants
- `getVisitorId()` exporté depuis l'API client React

P4 — Transparence éditoriale :
- Page `/contributions-brutes` avec pagination et export JSON/CSV
- `GET /api/contributions`, `GET /api/contributions/export/{json,csv}`
- `GET /api/stats/public` — stats publiques sans données de rejet
- Label de transparence IA sur la colonne de synthèse
- Compteurs (acceptées / soumises) dans le bandeau d'intro
- `docs/PROMPTS_IA.md` — prompts intégraux publiés + analyse des biais
- Pied de page avec liens légaux et transparence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:30:30 +02:00
billisdeadandClaude Sonnet 4.6 45edc1fa77 Licence EUPL-1.2 + hardening anti-abus
P1 — Licence :
- Ajout du fichier LICENSE (EUPL-1.2 complet)
- README mis à jour : section licence, table docs, vars d'environnement
- En-têtes EUPL ajoutés dans les fichiers sources principaux (Flask, React)

P2 — Hardening anti-abus :
- Rate limiting Redis-ready (REDIS_URL) avec clé fingerprint + IP
- Honeypot anti-bot : champ caché côté client + vérification serveur
- Fingerprinting non-PII via FingerprintJS (hash SHA-256, colonne ideas.fingerprint_hash)
- Cooldown session : cookie httpOnly signé HMAC-SHA256 (SECRET_KEY requis)
- Détection de flood : alerte WARNING si > FLOOD_THRESHOLD soumissions / 5 min
- hCaptcha stub : intégré, activable via HCAPTCHA_SECRET_KEY + VITE_HCAPTCHA_SITE_KEY
- Nouvelles dépendances : redis (backend), @fingerprintjs/fingerprintjs + @hcaptcha/react-hcaptcha (frontend)
- docs/SECURITE_ANTI_ABUS.md : documentation complète des seuils et de la configuration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:05:46 +02:00
pironantoine 2a792cbbb5 Add secure admin panel for content moderation and contribution flagging
Adds an admin interface with authentication for manual content deletion and flagging. Implements a flagging system for user contributions and secures the admin panel with a secret token.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7e5834b1-796d-4a9e-bbde-cd91012292de
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/nghZcOj
Replit-Helium-Checkpoint-Created: true
2026-04-05 03:42:58 +00:00
pironantoine ae970b2a32 Update political idea platform to use Python Flask backend
Replace the existing Node.js API server with a Python Flask application, implementing robust AI-driven content filtering based on international human rights law and enhancing security measures.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 30f4e946-427f-4b27-989d-531b9116d12f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/AWHAa3Z
Replit-Helium-Checkpoint-Created: true
2026-04-03 16:58:47 +00:00