Files
benevoles/backend/app/migrations/versions/0019_form_access_token.py
T
AdamThomasandClaude Sonnet 5 096b7cd26a
ci-cd / build-backend (push) Successful in 1m12s
ci-cd / build-frontend (push) Successful in 18s
ci-cd / deploy (push) Successful in 12s
Lien d'inscription protégé par jeton, reçus fiscaux mécénat, infobulles et revue de sécurité
- Lien d'inscription protégé par un jeton (FormTemplate.access_token,
  /inscription/<token>), avec régénération depuis l'écran de configuration.
- Reçus fiscaux mécénat (cerfa 11580*05) : un PDF par bénévole ayant payé en
  mécénat, donateur/montant (chiffres et toutes lettres en français,
  app/mecenat/number_words.py)/date complétés par-dessus le modèle officiel.
- Jeu de démonstration : bénévoles avec cotisation classique ou mécénat payée.
- Infobulles (HelpTip et détail de cellule de la grille des besoins)
  repositionnées en position: fixed via JS, bornées à l'écran.
- Revue de sécurité : logo des emails en pièce jointe inline au lieu d'une
  URL (fuyait le jeton d'inscription), sujet retiré du log de remise des
  emails (pouvait porter un nom de bénévole), photo du formulaire public
  revalidée par décodage réel avant stockage, export Excel protégé contre
  l'injection de formule, sondage du statut de paiement limité en
  fréquence, pyjwt mis à jour.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-02 07:47:17 +02:00

45 lines
1.6 KiB
Python

"""Jeton d'accès au formulaire public (lien non devinable)
Revision ID: 0019_form_access_token
Revises: 0018_pending_submissions_mecenat
Create Date: 2026-09-30
`form_template.access_token` : secret partagé désormais nécessaire pour
atteindre le formulaire public (`/inscription/<jeton>`) — voir CLAUDE.md § 2,
`FormTemplate`, et `app/api/forms.py:_require_access_token`. `/inscription`
seul, sans jeton dans l'URL, ne mène plus nulle part.
Rétrocompatibilité : chaque `form_template` déjà en base reçoit un jeton
généré aléatoirement — les liens déjà partagés cessent de fonctionner, la
coordination doit en repartager un nouveau (« Copier le lien » sur l'écran
Inscriptions) — c'est précisément le but du changement.
"""
import secrets
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "0019_form_access_token"
down_revision: str | None = "0018_pending_submissions_mecenat"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"form_template", sa.Column("access_token", sa.String(), nullable=True)
)
conn = op.get_bind()
for (row_id,) in conn.execute(sa.text("SELECT id FROM form_template")):
conn.execute(
sa.text("UPDATE form_template SET access_token = :token WHERE id = :id"),
{"token": secrets.token_urlsafe(16), "id": row_id},
)
op.alter_column("form_template", "access_token", nullable=False)
def downgrade() -> None:
op.drop_column("form_template", "access_token")