01-architecture-overview.md 7961 octets

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 un acct: ActivityPub en Uuid local.
  • 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

FeatureEffetÉtat
defaultAuth JWT, RBAC, TOTP, OAuth (si activé), sessions mémoire/SeaORMStable
oauthActive openidconnect + reqwest — providers Google, GitHub, Microsoft, DiscordStable
redis-sessionRemplace le backend de sessions par tower-sessions-redis-storeNe 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)