Étape 7 : authentification par lien magique + JWT + rôles
- app/auth/ : jetons (lien magique aléatoire haché SHA-256, JWT HS256), dépendances get_current_account / current_account_in_edition / require_roles + require_coordinator / require_staff, et ensure_can_write_position/_shift - Endpoints app/api/auth.py : POST .../auth/magic-link (202 constant, pas de divulgation), POST .../auth/verify (JWT, jeton à usage unique), GET /auth/me - Envoi d'email stubé : le lien est journalisé (logger app.auth, identifiants seulement, aucune donnée nominative) - app/api/accounts.py : CRUD des comptes réservé à la COORDINATION - Rôles appliqués sur toute l'API : COORDINATION accès complet ; RÉFÉRENT lecture globale + écriture sur ses seuls postes/créneaux/affectations ; BÉNÉVOLE sa fiche et ses affectations, aucune écriture - imports.py aligné sur la dépendance get_edition (404 avant 403) - Secret JWT porté à >= 32 octets (config, .env.example, docker-compose) - seed : crée un compte VOLUNTEER pour chaque bénévole avec email (104 comptes) - conftest : fixtures make_account / coordinator / as_account / raw_client ; la suite existante s'exécute en COORDINATION - 13 tests d'authentification + de rôles ; 119 tests au total ; alembic check toujours vert (Account / MagicLinkToken déjà dans la migration 0001) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c9dda65361
commit
ced922f785
@@ -95,17 +95,40 @@ Conventions techniques :
|
||||
- Dates/heures **naïves**, heure locale Europe/Paris (`TZ` du conteneur +
|
||||
`tzdata`). `app.models.local_now()` pour les horodatages.
|
||||
|
||||
### Rôles et accès
|
||||
### Rôles et accès (`app/auth/`)
|
||||
|
||||
- **`COORDINATOR`** : accès complet, construit et publie les plannings.
|
||||
- **`LEAD`** (référent) : lecture sur toute l'édition, écriture limitée à ses
|
||||
propres postes.
|
||||
- **`VOLUNTEER`** : accès à ses seules données.
|
||||
- **`LEAD`** (référent) : lecture sur toute l'édition, écriture limitée aux
|
||||
postes dont il est `lead_account_id` (et à leurs créneaux et affectations).
|
||||
- **`VOLUNTEER`** : lecture de sa seule fiche et de ses seules affectations ;
|
||||
aucune écriture.
|
||||
|
||||
Authentification par lien magique envoyé par email, sans mot de passe. L'envoi
|
||||
d'email est **stubé** pour l'instant (le lien est écrit dans les logs). Un
|
||||
bénévole sans email n'a pas de `Account` et ne se connecte pas ; il est géré
|
||||
entièrement par la coordination.
|
||||
Matrice appliquée par les endpoints :
|
||||
|
||||
| Ressource | COORDINATOR | LEAD | VOLUNTEER |
|
||||
|-----------|-------------|------|-----------|
|
||||
| `volunteers` liste/écriture | ✔ | liste seule | — |
|
||||
| `volunteers/{id}` lecture | ✔ | ✔ | soi-même |
|
||||
| `positions`/`shifts` lecture | ✔ | ✔ | — |
|
||||
| `positions`/`shifts` écriture | ✔ | ses postes | — |
|
||||
| `assignments` écriture | ✔ | ses postes | — |
|
||||
| `assignments` lecture | ✔ | ✔ | les siennes |
|
||||
| `matrix`, `import`, `accounts` | ✔ / ✔ / ✔ | ✔ / — / — | — |
|
||||
|
||||
**Authentification** : lien magique + session JWT (`Bearer`), sans mot de passe.
|
||||
|
||||
1. `POST /editions/{id}/auth/magic-link` `{email}` → réponse `202` identique
|
||||
qu'un compte existe ou non. Si oui, un `MagicLinkToken` est créé (jeton
|
||||
aléatoire, **haché** en base, TTL `magic_link_ttl_minutes`).
|
||||
2. **Envoi d'email stubé** : le lien est écrit dans les logs (`app.auth`,
|
||||
niveau INFO, `jeton=…` — identifiants seulement, aucune donnée nominative).
|
||||
3. `POST /editions/{id}/auth/verify` `{token}` → JWT HS256
|
||||
(`session_ttl_hours`) + `AccountRead`. Jeton à usage unique.
|
||||
4. `GET /auth/me` → compte courant. En-tête `Authorization: Bearer <jwt>`.
|
||||
|
||||
Un bénévole sans email n'a pas de `Account` et ne se connecte pas ; il est géré
|
||||
entièrement par la coordination. La coordination crée les comptes via
|
||||
`POST /editions/{id}/accounts`.
|
||||
|
||||
---
|
||||
|
||||
@@ -188,8 +211,10 @@ pointage sur site · fonctions d'IA.
|
||||
│ │ ├── db.py # engine + session
|
||||
│ │ ├── logging_config.py # RGPD : pas de PII dans les logs
|
||||
│ │ ├── api/ # routers + schemas Pydantic + deps
|
||||
│ │ │ # health, imports, volunteers, positions,
|
||||
│ │ │ # shifts, assignments, matrix
|
||||
│ │ │ # health, auth, accounts, imports,
|
||||
│ │ │ # volunteers, positions, shifts,
|
||||
│ │ │ # assignments, matrix
|
||||
│ │ ├── auth/ # lien magique + JWT + dépendances de rôle
|
||||
│ │ ├── importing/ # import CSV/Excel (parser, values, service)
|
||||
│ │ ├── models/ # tables SQLModel
|
||||
│ │ ├── validation/ # moteur pur (models, engine) + loader (base)
|
||||
@@ -268,10 +293,11 @@ Aucune dépendance locale requise hors Docker.
|
||||
| 3 | Import CSV/Excel (analyse + exécution, rapport, doublons, simulation) | validée |
|
||||
| 4 | Moteur de validation des affectations (pur, TDD, 5 règles) | validée |
|
||||
| 5 | API CRUD (Position, Shift, Volunteer, Assignment) + matrice + validation branchée | validée |
|
||||
| 6 | Jeu de données de démonstration (`app/seed.py`) | à valider |
|
||||
| 6 | Jeu de données de démonstration (`app/seed.py`) | validée |
|
||||
| 7 | Authentification : lien magique + JWT + application des rôles | à valider |
|
||||
|
||||
Premier incrément backend terminé. **Reste hors périmètre** (modèle prêt à les
|
||||
accueillir) : authentification par lien magique (`Account` + `MagicLinkToken`
|
||||
existent, endpoints non encore protégés), solveur OR-Tools, envoi
|
||||
d'emails/SMS, constructeur de formulaire, pointage, IA. Le frontend
|
||||
(React/Vite/TS/Tailwind) reste à démarrer.
|
||||
Premier incrément backend + authentification terminés. **Reste hors
|
||||
périmètre** (modèle prêt à les accueillir) : envoi réel d'emails/SMS (le lien
|
||||
magique est journalisé, pas envoyé), solveur OR-Tools, constructeur de
|
||||
formulaire, pointage sur site, IA. Le frontend (React/Vite/TS/Tailwind) reste
|
||||
à démarrer.
|
||||
|
||||
Reference in New Issue
Block a user