0012-fediverse-inbox-route.md 7707 octets

RFC 0012 — Fediverse: route HTTP /inbox lifecycle (slice routes 1/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-core qui veut recevoir les signaux AP Update{Actor} / Delete{Actor} / Revoke{Membership} envoyés par les serveurs distants.
  • Plan associé : docs/plans/2026-05-09-fediverse-identity-federation.md, Phase 4 — clôture HTTP.
  • Dépendances : RFC 0010 (FediverseSignalService + SignatureVerifier).

Summary

Exposer la route HTTP POST /inbox (montée par with_fediverse() côté RustwardenBuilder) qui :

  1. Lit le Signature header HTTP.
  2. Délègue la vérification au SignatureVerifier injecté côté config (par défaut AlwaysAcceptVerifier pour test/dev — production attend la RFC crypto à venir).
  3. Désérialise le body JSON-LD, dispatch sur le type de l'activité (Update, Delete, Revoke) vers la méthode correspondante de FediverseSignalService.
  4. Retourne 202 Accepted (livraison asynchrone) ou un code d'erreur explicite (401/400/501).

Les routes auth-flow (5 endpoints) et team-invite (3 endpoints) sont explicitement reportées à des RFCs séparées (0013, 0014) pour rester reviewable.

Motivation

Sans cette RFC, FediverseSignalService (livré par RFC 0010) n'a aucun chemin d'entrée HTTP. Mastodon et compagnie ne peuvent pas notifier l'instance qu'un actor distant a été supprimé / mis à jour, et la promesse « Delete{Actor} → sessions invalidées en moins de 30 s » du master plan reste théorique.

Non-goals

  • Pas d'autres routes dans cette RFC (auth-flow et invite restent reportées).
  • Pas d'implémentation HTTP-Sig RFC 9421 réelle — seul le trait SignatureVerifier (RFC 0010) est consommé. La route refuse toute requête si le Signature header est absent ; en mode AlwaysAcceptVerifier, la signature elle-même n'est pas vérifiée — le warning tracing::warn de AlwaysAcceptVerifier est suffisant pour signaler la non-prod.
  • Pas de UI ni de page admin pour visualiser les signaux reçus.
  • Pas de support Authorization Bearer (les inbox AP ne s'authentifient pas via Bearer mais via HTTP-Sig).
  • Pas de gestion des activités inconnues au-delà du 501 Not Implemented documenté.

API proposée

Handler

#[derive(Debug, Deserialize)]
pub struct InboxActivity {
    #[serde(rename = "type")]
    pub kind: String,
    pub actor: Option<String>,
    pub object: Option<serde_json::Value>,
    pub id: Option<String>,
}

/// `POST /inbox` handler.
///
/// Reads `Signature` header, delegates verification to the configured
/// `SignatureVerifier`, then dispatches based on `kind`.
pub async fn handle_inbox(
    Extension(state): Extension<Arc<InboxState>>,
    Extension(db): Extension<DatabaseConnection>,
    headers: axum::http::HeaderMap,
    body: axum::body::Bytes,
) -> Result<axum::http::StatusCode, FediverseError>;

InboxState

pub struct InboxState {
    pub verifier: Arc<dyn SignatureVerifier>,
    pub config: FediverseConfig,
}

Dispatch table

type du bodyActionMéthode service
Update (object.type = Person)upsert remote_identitiesapply_update_actor
Delete (object = ap_id de l'actor)flip disabled_atapply_delete_actor
Revoke (object.type = TeamMembership)drop team_members rowapply_revoke_membership
autrelog + 501 Not Implemented—

Réponses HTTP

  • 202 Accepted sur dispatch réussi.
  • 400 Bad Request si JSON invalide ou champs requis manquants.
  • 401 Unauthorized si le Signature header est absent ou si SignatureVerifier::verify retourne Err.
  • 501 Not Implemented si kind n'est pas dans la table de dispatch.

Wiring RustwardenBuilder::with_fediverse

Le builder stocke un nouveau champ signature_verifier: Option<Arc<dyn SignatureVerifier>> (par défaut Arc::new(AlwaysAcceptVerifier)). La méthode chaînable .with_signature_verifier(verifier) permet aux apps consommatrices de plugger leur impl HTTP-Sig réelle dès qu'elle existe.

Analyse de compatibilité

  • Non-breaking : nouvelle route + champ optionnel dans RustwardenBuilder.
  • Aucun changement de schéma DB (RFC 0010 a déjà tout fait).
  • Aucune API publique existante n'est modifiée.

Alternatives considérées

Alternative A (recommandée) — /inbox unique avec dispatch par type

Pros : conforme à AP/Mastodon (single inbox per actor + shared inbox). Simple.

Cons : un changement de format AP futur (e.g. Tombstone au lieu de Delete) demande de modifier le dispatch.

Alternative B — /inbox/{type} (route par activité)

Cons : non-conforme AP, casse Mastodon.

Rejeté.

Alternative C — Pas de route, exposer le service en lib seulement

Cons : annule l'intérêt de RFC 0010 pour les apps qui veulent un drop-in.

Rejeté.

Alternative D — Inclure auth-flow + team-invite dans la même RFC

Cons : ~1500 lignes, surface de revue énorme.

Rejeté.

Testing plan

8 tests :

  1. inbox_returns_202_for_valid_update_actor — body bien formé, dispatch réussi.
  2. inbox_returns_202_for_valid_delete_actor.
  3. inbox_returns_202_for_valid_revoke_membership.
  4. inbox_returns_401_when_signature_header_missing.
  5. inbox_returns_401_when_verifier_rejects — fake verifier qui retourne Err.
  6. inbox_returns_400_for_invalid_json.
  7. inbox_returns_501_for_unknown_activity_type.
  8. inbox_smoke_test_with_always_accept_verifier.

Tests via axum::Router + tower::ServiceExt::oneshot.

Migration guide

### Added (rustwarden-core, feature `experimental-fediverse`)

- Route HTTP `POST /inbox` (montée par `with_fediverse()`).
- Type public `InboxState`.
- Méthode chaînable `RustwardenBuilder::with_signature_verifier(verifier)`.

### Migration steps

1. Aucun changement requis pour les apps qui n'activent pas la feature.
2. Les apps fediverse existantes : par défaut `AlwaysAcceptVerifier` est
   utilisé (warning loggé). Quand la RFC HTTP-Sig réelle sera livrée,
   appeler `.with_signature_verifier(Arc::new(HttpSigVerifier::new(…)))`
   avant `.build()` pour activer la vérification réelle.

Open questions

Toutes résolues à l'auto-revue :

  • Path : /inbox (no-prefix, conforme Mastodon discovery par défaut). Les actors locaux pointent vers ce path via PersonJson::inbox.
  • Body limit : Axum body limit par défaut. Pas de limite custom dans cette RFC.
  • Status code 202 vs 200 : 202 Accepted car certains traitements (e.g. blacklist JWT) sont asynchrones.

Tracking issue

À ouvrir au moment de la PR.


Score d'évaluation

CritèreNote (1-5)Commentaire
Généralité4Pattern réutilisable pour toute future activité AP entrante.
Stabilité API5Additif, gated, aucun impact sur APIs existantes.
Performance4Dispatch inline, pas de DB lookup avant la signature.
Sécurité3AlwaysAcceptVerifier par défaut — explicite mais à substituer en prod.
Complexité d'implémentation4~300 lignes (handler + state + 8 tests).
Maintenance3Dispatch table à étendre quand de nouveaux types arrivent.
Documentation3Rustdoc EN, doctest, CHANGELOG.
Total26/35≥ 25 → Accepted