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>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"""
|
||||
Couche d'accès à la base de données PostgreSQL.
|
||||
La Voix du Peuple — Couche d'accès à la base de données PostgreSQL
|
||||
Copyright (C) 2026 billisdead — Licence EUPL-1.2
|
||||
|
||||
Utilise psycopg2 directement — pas d'ORM, code lisible et transparent.
|
||||
"""
|
||||
import os
|
||||
@@ -32,7 +34,7 @@ def db_cursor():
|
||||
conn.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
def init_db() -> None:
|
||||
"""Crée les tables si elles n'existent pas, et applique les migrations nécessaires."""
|
||||
with db_cursor() as cur:
|
||||
cur.execute("""
|
||||
@@ -46,10 +48,12 @@ def init_db():
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
""")
|
||||
# Migrations incrémentales — idempotentes
|
||||
cur.execute("ALTER TABLE ideas ADD COLUMN IF NOT EXISTS legal_basis TEXT")
|
||||
cur.execute("ALTER TABLE ideas ADD COLUMN IF NOT EXISTS flagged BOOLEAN NOT NULL DEFAULT FALSE")
|
||||
cur.execute("ALTER TABLE ideas ADD COLUMN IF NOT EXISTS flag_count INTEGER NOT NULL DEFAULT 0")
|
||||
cur.execute("ALTER TABLE ideas ADD COLUMN IF NOT EXISTS admin_note TEXT")
|
||||
cur.execute("ALTER TABLE ideas ADD COLUMN IF NOT EXISTS fingerprint_hash VARCHAR(64)")
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS synthesis (
|
||||
id SERIAL PRIMARY KEY,
|
||||
@@ -61,16 +65,22 @@ def init_db():
|
||||
logger.info("Base de données initialisée.")
|
||||
|
||||
|
||||
def insert_idea(content: str, author: str | None, accepted: bool,
|
||||
rejection_reason: str | None, legal_basis: str | None) -> dict:
|
||||
def insert_idea(
|
||||
content: str,
|
||||
author: str | None,
|
||||
accepted: bool,
|
||||
rejection_reason: str | None,
|
||||
legal_basis: str | None,
|
||||
fingerprint_hash: str | None = None,
|
||||
) -> dict:
|
||||
with db_cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO ideas (content, author, accepted, rejection_reason, legal_basis)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
INSERT INTO ideas (content, author, accepted, rejection_reason, legal_basis, fingerprint_hash)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
RETURNING *
|
||||
""",
|
||||
(content, author, accepted, rejection_reason, legal_basis),
|
||||
(content, author, accepted, rejection_reason, legal_basis, fingerprint_hash),
|
||||
)
|
||||
return dict(cur.fetchone())
|
||||
|
||||
@@ -91,8 +101,12 @@ def get_all_ideas(limit: int = 50) -> list[dict]:
|
||||
return [dict(row) for row in cur.fetchall()]
|
||||
|
||||
|
||||
def get_ideas_admin(status: str = "all", page: int = 1,
|
||||
per_page: int = 50, search: str = "") -> tuple[list[dict], int]:
|
||||
def get_ideas_admin(
|
||||
status: str = "all",
|
||||
page: int = 1,
|
||||
per_page: int = 50,
|
||||
search: str = "",
|
||||
) -> tuple[list[dict], int]:
|
||||
offset = (page - 1) * per_page
|
||||
conditions = []
|
||||
params: list = []
|
||||
@@ -138,8 +152,12 @@ def bulk_delete_ideas(idea_ids: list[int]) -> int:
|
||||
return len(cur.fetchall())
|
||||
|
||||
|
||||
def override_idea(idea_id: int, accepted: bool,
|
||||
reason: str | None, note: str | None) -> dict | None:
|
||||
def override_idea(
|
||||
idea_id: int,
|
||||
accepted: bool,
|
||||
reason: str | None,
|
||||
note: str | None,
|
||||
) -> dict | None:
|
||||
with db_cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user