RFC 0014 — Fediverse: routes HTTP des invitations cross-instance (slice routes 3/N)
- Auteur : EBO (rustwarden)
- Date : 2026-05-10
- Statut : Accepted (auto-revue mainteneur unique 2026-05-10, score 26/35)
- Apps consommatrices concernées : toute app utilisant
rustwarden-corequi veut permettre à un admin d'équipe d'inviter un utilisateur fediverse externe via HTTP/JSON, et à un IdP de gérer les invitations reçues. - Plan associé :
docs/plans/2026-05-09-fediverse-identity-federation.md, Phase 3 — clôture HTTP. - Dépendances : RFC 0009 (
FediverseInviteService), RFC 0012/0013 (pattern routes).
Summary
Exposer 6 routes HTTP qui rendent FediverseInviteService (RFC 0009) utilisable end-to-end :
Côté b.tld (admin de l'équipe) :
POST /api/v1/teams/{team_id}/members/invite-fediverse— démarre une invitation.GET /api/v1/teams/{team_id}/invites— liste les invitations pending.POST /api/v1/teams/{team_id}/invites/{invite_id}/revoke— révocation.
Côté a.tld (IdP, utilisateur invité) :
GET /api/v1/fediverse/invites— mes invitations en attente.POST /api/v1/fediverse/invites/{invite_id}/accept— accepte.POST /api/v1/fediverse/invites/{invite_id}/reject— refuse.
Chaque handler est un thin wrapper autour du service. Aucune UI livrée.
Motivation
Sans cette RFC, RFC 0009 reste sans chemin HTTP : un admin doit appeler le service Rust à la main pour inviter un user fediverse, et l'IdP n'a aucun moyen de présenter les invitations reçues. Le master plan §10 Phase 3 « b.tld invite alice@a.tld via UI → Alice voit l'invitation sur son IdP » devient enfin réalisable côté API JSON.
Non-goals
- Pas de UI HTML (
/admin/teams/{id}enrichie +/profile/invites) — Phase 3 finale UI. - Pas d'authentification dans cette RFC — les apps consommatrices appliquent leur middleware Bearer JWT habituel ; les handlers extraient
Claimsvia l'extracteur existantAuth(Claims)côtéb.tld, et acceptent unIdentity::Remotecôté IdP. Une vérification que l'identity correspond bien au handle de l'invite est faite par le service. - Pas de listing pagination —
list_pending_for_teamretourne tous les pending. Pagination différée si besoin (volume faible attendu). - Pas de routes resource-share (
shared_with_remote_identity_id) — un follow-up RFC ajoutera l'endpoint quandresource_service::share_with_remote_identitysera livré.
API proposée
Bodies / responses JSON
// POST /api/v1/teams/{team_id}/members/invite-fediverse #[derive(Deserialize)] pub struct InviteBody { pub handle: String, pub role: String /* "member"|"admin" */ } #[derive(Serialize)] pub struct InviteResponse { pub invite_id: Uuid, pub activity_id: String, pub expires_at: String } // GET /api/v1/teams/{team_id}/invites #[derive(Serialize)] pub struct InviteRow { pub id: Uuid, pub handle: String, pub role: String, pub status: String, pub created_at: String, pub expires_at: String, } // IdP-side accept/reject — empty body, just a path id.
Câblage
fediverse_invite_routes::router(state) ajouté au routes::router_with_auth après auth_routes::router. Pas de nouvel Extension requis : tous les handlers utilisent le DatabaseConnection injecté.
États HTTP
| Endpoint | Succès | Erreurs |
|---|---|---|
invite-fediverse | 201 Created | 400 handle/role invalide |
invites (list) | 200 OK | — |
invites/{id}/revoke | 200 OK | 404 invite inconnu, 400 non-revocable |
/fediverse/invites (IdP) | 200 OK | — |
accept | 200 OK | 404, 400 non-pending |
reject | 200 OK | 404, 400 non-pending |
Analyse de compatibilité
-
Non-breaking : 6 nouveaux paths gated par
experimental-fediverse. -
Aucune modification de l'API
team_serviceau-delà de RFC 0009. - Aucun changement de schéma DB.
Alternatives considérées
Alternative A (recommandée) — 6 endpoints REST minces
Pros : conforme aux conventions existantes ; chaque endpoint a un audit clair.
Cons : 6 paths à mémoriser.
Alternative B — Endpoint unique /api/v1/fediverse/invites/{id} avec champ decision
Cons : pollue le path avec une logique d'action ; rate-limit / audit moins clair.
Rejeté.
Alternative C — IdP-side via le même path que côté admin
Cons : confusion de responsabilités, erreurs d'authz.
Rejeté.
Alternative D — Ne rien faire
Cons : Phase 3 reste théorique côté HTTP.
Rejeté.
Testing plan
12 tests :
invite_returns_201_with_invite_id.invite_returns_400_for_invalid_role.list_pending_returns_200_with_array.revoke_returns_200_for_pending_invite.revoke_returns_404_for_unknown_invite.idp_list_returns_200_with_invites_for_remote_identity.idp_accept_returns_200_for_pending.idp_accept_returns_400_for_already_accepted.idp_reject_returns_200_for_pending.idp_reject_returns_400_for_already_decided.invite_uses_default_ttl_from_fediverse_config.idp_list_returns_empty_array_when_no_invites.
Tests via axum::Router + tower::ServiceExt::oneshot.
Migration guide
### Added (rustwarden-core, feature `experimental-fediverse`) - 6 routes HTTP : 3 admin + 3 IdP. - Module `rustwarden_core::fediverse::invite_routes`. - Bodies / responses : `InviteBody`, `InviteResponse`, `InviteRow`. ### Migration steps 1. Aucun changement obligatoire — gating feature. 2. Apps consommatrices : peuvent maintenant piloter le flow invitation via JSON depuis n'importe quel SPA / app mobile.
Open questions
Toutes résolues à l'auto-revue :
-
TTL invitations : 7 jours par défaut (paramétrable via
FediverseConfigplus tard, hors scope ici). -
Path IdP :
/api/v1/fediverse/invites(pas/api/v1/me/invitespour rester cohérent avec les autres routes fediverse). -
Pagination : différée. Si nécessaire, ajouter
?cursor=…&limit=…dans une RFC suivante.
Tracking issue
À ouvrir au moment de la PR.
Score d'évaluation
| Critère | Note (1-5) | Commentaire |
|---|---|---|
| Généralité | 4 | Pattern réutilisable pour d'autres types d'invitations (resource-share). |
| Stabilité API | 4 | Additif, gated. |
| Performance | 4 | Lookups indexés (fediverse_pending_invites_status_idx). |
| Sécurité | 3 | Authz déléguée aux apps consommatrices via leur middleware Bearer. |
| Complexité d'implémentation | 4 | ~500 lignes (6 handlers + state + 12 tests). |
| Maintenance | 3 | 6 paths à conserver compatibles. |
| Documentation | 4 | Rustdoc EN, doctest, CHANGELOG, migration guide. |
| Total | 26/35 | ≥ 25 → Accepted |
GitRust