Architecture de Rustwarden
Vision
Rustwarden est conçu selon trois principes non négociables.
Modulaire. Chaque fonctionnalité vit dans une crate dédiée. On peut intégrer uniquement rustwarden-core (API JSON headless), y ajouter rustwarden-ui (interface HTML), puis rustwarden-fediverse (ActivityPub) de façon totalement indépendante. Les crates compagnons ne créent jamais de dépendance inversée vers rustwarden-core — elles s'y branchent via des traits.
Headless-first. L'API REST JSON est la surface primaire. L'interface HTML (rustwarden-ui) est un consommateur comme un autre. Un projet SPA ou mobile n'a besoin que de rustwarden-core.
Extension via traits. Le consumer n'est pas obligé de forker le framework pour réagir aux événements métier. Il implémente RustwardenHooks et passe l'objet au builder. La fédération s'accroche de la même façon via LocalUserResolver, SignatureVerifier et FediverseEventObserver.
Carte des crates
flowchart LR
APP([rustwarden-app\nbinaire de référence])
subgraph CORE ["rustwarden-core"]
CORE_NODE[auth · RBAC · services\nmiddleware · i18n · hooks]
end
subgraph UI ["rustwarden-ui"]
UI_NODE[templates Askama\nHTMX · DaisyUI]
end
subgraph FEDI ["rustwarden-fediverse"]
FEDI_NODE[ActivityPub headless\nacteur · inbox · signatures]
end
subgraph FEDI_UI ["rustwarden-fediverse-ui"]
FEDI_UI_NODE[handlers HTML fédération\nconsentement · invitations]
end
subgraph GUARD ["rustwarden-fediverse-guard"]
GUARD_NODE[détection d'abus\nrate-limit fédéré]
end
APP --> CORE_NODE
APP --> UI_NODE
UI_NODE --> CORE_NODE
FEDI_UI_NODE --> FEDI_NODE
FEDI_NODE --> CORE_NODE
GUARD_NODE --> FEDI_NODE
rustwarden-core n'a aucune connaissance des crates au-dessus de lui. La flèche va toujours vers le bas.
Vue en couches
flowchart TB
subgraph PRES ["Couche présentation"]
T[Templates Askama + HTMX]
H[Handlers HTML — rustwarden-ui / fediverse-ui]
end
subgraph API ["Couche API"]
R[Routeur Axum]
E[Extracteurs : AuthUser · Json · Form · Locale]
end
subgraph MW ["Couche middleware"]
MW1[auth_required · optional_auth]
MW2[CSRF · rate_limit]
MW3[admin_required · revocation]
end
subgraph SVC ["Couche services"]
S1[AuthService · JwtService · JwtBlacklistService]
S2[UserService · RoleService · PermissionService]
S3[RefreshTokenService · TotpService · OAuthService]
S4[TeamService · ResourceService · EmailService]
end
subgraph PERSIST ["Couche persistance"]
M[Entités SeaORM]
MIG[Migrations sea-orm-migration]
end
subgraph INFRA ["Couche infrastructure"]
DB[(PostgreSQL)]
REDIS[(Redis — optionnel)]
SMTP[SMTP · IMAP]
end
PRES --> API
API --> MW
MW --> SVC
SVC --> PERSIST
PERSIST --> INFRA
Chaque couche ne connaît que la couche immédiatement inférieure. Les handlers appellent les services, jamais les entités SeaORM directement.
Trois axes d'extension
1. Hooks métier — RustwardenHooks
Le trait RustwardenHooks expose des méthodes appelées par le framework après chaque événement significatif. Toutes ont une implémentation par défaut no-op.
use rustwarden_core::hooks::RustwardenHooks; use async_trait::async_trait; use sea_orm::DatabaseConnection; use uuid::Uuid; struct MyHooks; #[async_trait] impl RustwardenHooks for MyHooks { async fn on_user_login( &self, db: &DatabaseConnection, user_id: Uuid, username: &str, ) -> anyhow::Result<()> { // journaliser dans une table métier, envoyer une notification… Ok(()) } } // Branchement au builder : RustwardenApp::builder() .from_env() .hooks(MyHooks) .build() .await? .run() .await
Événements disponibles : on_user_registered, on_user_login, on_user_logout, on_password_changed, on_email_verified, on_role_changed, on_resource_shared, on_resource_unshared, on_user_deleted, on_totp_enabled, on_totp_disabled, on_audit_event.
2. Traits fédération
rustwarden-fediverse se branche sur rustwarden-core via trois traits définis dans core (pas dans fediverse) :
LocalUserResolver— résout unacct:ActivityPub enUuidlocal.SignatureVerifier— vérifie les signatures HTTP des requêtes AP entrantes.FediverseEventObserver— reçoit les activités (Follow, Accept, Like…) délivrées à l'inbox.
Ce modèle garantit que rustwarden-core compile sans rustwarden-fediverse dans le graph de dépendances.
3. Feature flags Cargo
| Feature | Effet | État |
|---|---|---|
default | Auth JWT, RBAC, TOTP, OAuth (si activé), sessions mémoire/SeaORM | Stable |
oauth | Active openidconnect + reqwest — providers Google, GitHub, Microsoft, Discord | Stable |
redis-session | Remplace le backend de sessions par tower-sessions-redis-store | Ne compile pas actuellement — éviter en CI |
Flux d'une requête type
sequenceDiagram
participant C as Client
participant R as Routeur Axum
participant MW as Middleware auth_required
participant BL as JwtBlacklistService
participant JWT as JwtService
participant H as Handler
participant SVC as Service métier
participant DB as PostgreSQL
C->>R: GET /api/v1/resource (Authorization: Bearer <token>)
R->>MW: vérification auth requise
MW->>BL: is_blacklisted(token_hash)
BL-->>MW: false
MW->>JWT: validate_token(token)
JWT-->>MW: Claims { sub, roles, … }
MW->>H: request + Claims en extension
H->>SVC: appel service métier
SVC->>DB: requête SeaORM
DB-->>SVC: résultat
SVC-->>H: donnée métier
H-->>C: 200 JSON / HTML
Pour les routes HTML protégées (/home, /profile, /admin…), le middleware auth_required redirige vers /login au lieu de retourner 401 si le token est absent.
Builder API — aperçu des méthodes
Le builder RustwardenBuilder s'utilise de façon fluide. Voici les méthodes essentielles :
RustwardenApp::builder() // Configuration .from_env() // charge .env, initialise tracing .database_url("postgres://…") // ou .database(conn) si déjà connecté .address("0.0.0.0:3000") // Fonctionnalités optionnelles .with_ui(rustwarden_ui::default_routes) // active l'UI HTML .hooks(MyHooks) // hooks métier custom .headless() // API JSON uniquement (sans UI) .auto_migrate(true) // migrations au démarrage .without_email() // désactive SMTP .without_background_tasks() // désactive tâches de fond // Routes supplémentaires .merge_routes(my_app_router()) // Fédération (injecté par rustwarden_fediverse::mount) .register_protected_prefix("/fediverse/consent") .register_revocation_checker(Arc::new(my_checker)) .add_dashboard_card(fediverse_dashboard_card()) // Localisation .locale_dir("locales/") .supported_locales(vec!["fr".into(), "en".into()]) .default_locale("fr") .build().await? .run().await
Après .build(), l'objet RustwardenApp expose :
.database()— pool SeaORM partagé (pour les migrations métier).auth_config()— configuration JWT/cookies.i18n_service()— service de traduction.address()— adresse d'écoute.into_router()— routeur assemblé (pour les tests d'intégration)
GitRust