Files
benevoles/backend/app/auth/magic_link.py
T
AdamThomasandClaude Sonnet 5 b268e308ab Étape 10 : envoi d'emails par SMTP
- app/email/ : sender (smtplib, STARTTLS ou SSL, connexion réutilisée pour un
  lot) + templates (rendu, textes dans locales/fr.json). EMAIL_ENABLED=false
  (défaut) ⇒ journalisé seulement, sans destinataire ni corps (RGPD).
- Lien magique : POST .../auth/magic-link envoie désormais l'email
  (PUBLIC_BASE_URL + magic_link_path + ?token=). Helper app/auth/magic_link.py
  partagé. Sans SMTP, le jeton non nominatif reste journalisé pour le dev.
- Récapitulatif de planning : POST /editions/{id}/notifications/planning
  (COORDINATION). subject/body sont des modèles ({prenom}, {nom}, {nb_creneaux},
  {nb_heures}, {creneaux}, {lien_connexion} → lien magique personnel émis).
  Cible tous les bénévoles avec email ou une liste. Envoi en BackgroundTasks.
- Config : PUBLIC_BASE_URL, EMAIL_ENABLED, SMTP_* ; .env.example,
  docker-compose (dev + prod), deploy/README.
- 9 tests (sender activé/désactivé, rendu, notifications : contenu, gabarit par
  défaut, lien magique embarqué, sous-ensemble, réservé COORDINATION).
  137 tests au total ; ruff + alembic check verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-10 08:08:55 +02:00

33 lines
1016 B
Python

"""Émission d'un jeton de lien magique et construction de l'URL publique.
Partagé par l'endpoint d'authentification et par les emails de planning."""
import datetime as dt
from sqlmodel import Session
from app.auth.tokens import hash_token, new_raw_token
from app.config import get_settings
from app.models import Account, MagicLinkToken, local_now
def issue_magic_link_token(session: Session, account: Account) -> str:
"""Crée un `MagicLinkToken` pour le compte et renvoie le jeton en clair.
N'effectue pas de commit."""
raw = new_raw_token()
session.add(
MagicLinkToken(
account_id=account.id,
token_hash=hash_token(raw),
expires_at=local_now()
+ dt.timedelta(minutes=get_settings().magic_link_ttl_minutes),
)
)
return raw
def build_magic_link_url(raw_token: str) -> str:
settings = get_settings()
base = settings.public_base_url.rstrip("/")
return f"{base}{settings.magic_link_path}?token={raw_token}"