API rustwarden-core — Référence

Ce document décrit les helpers, extracteurs Axum, middleware, et APIs d'extension fournis par le framework rustwarden-core que gitrust consomme.

Extracteurs Axum

AuthUser

Module : rustwarden_core::auth::extractors
Export : prélude

Extracteur qui valide le JWT depuis le cookie jwt_token ou le header Authorization: Bearer <token>. Retourne 401 Unauthorized si le token est absent, expiré, invalide, ou blacklisté.

use rustwarden_core::prelude::*;

async fn handler(user: AuthUser) -> impl IntoResponse {
    // Champs disponibles :
    let _id: Uuid = user.user_id;
    let _name: &str = &user.username;
    let _roles: &[String] = &user.roles;
    let _perms: &[String] = &user.permissions;
}

Méthodes de vérification :

Méthode Description
has_role(role: &str) → bool L'utilisateur a-t-il ce rôle ?
has_any_role(roles: &[&str]) → bool A-t-il au moins un des rôles ?
has_all_roles(roles: &[&str]) → bool A-t-il tous les rôles ?
has_permission(permission: &str) → bool A-t-il cette permission ? Le rôle admin retourne toujours true.

AuthenticatedSession

Module : rustwarden_core::auth::session

Extracteur similaire à AuthUser mais inclut les informations de session (device info, IP). Utilisé pour la page /settings/sessions.

async fn sessions_handler(
    State(db): State<DatabaseConnection>,
    session: AuthenticatedSession,
) -> impl IntoResponse {
    let current_token_id = session.token_id; // UUID du refresh token courant
    // Utilisé pour marquer "Current session" dans l'UI
}

OptionalUser

Extracteur qui ne rejette pas les requêtes non authentifiées — retourne Option<AuthUser>. Utilisé pour les pages publiques (dépôts publics, explore).

async fn public_page(user: OptionalUser) -> impl IntoResponse {
    match user.0 {
        Some(u) => format!("Bonjour {}", u.username),
        None => "Visiteur anonyme".to_owned(),
    }
}

Middleware

Middleware d'authentification JWT

Intégré automatiquement dans RustwardenApp::build(). Gère : - Lecture du JWT depuis Cookie: jwt_token=<JWT> ou Authorization: Bearer <JWT>. - Vérification de la signature HMAC-SHA256 avec la clé JWT_SECRET. - Vérification de l'expiration (exp). - Vérification de la blacklist (table jwt_blacklist).

Ce middleware ne bloque pas les requêtes anonymes — c'est l'extracteur AuthUser dans le handler qui décide si l'authentification est requise.

Middleware CSRF

Protège toutes les requêtes mutantes (POST, PUT, DELETE, PATCH) émises par des formulaires HTML. Le token CSRF est : - Généré à la connexion et stocké dans un cookie csrf_token (HttpOnly, SameSite=Strict). - Attendu dans le corps du formulaire sous le champ _csrf. - Absent ou invalide → 403 Forbidden.

Pour les endpoints API JSON (header Content-Type: application/json), la vérification CSRF est désactivée — l'authentification Bearer PAT/JWT suffit.

<!-- Dans tout formulaire POST -->
<input type="hidden" name="_csrf" value="{{ csrf_token }}">

Middleware de rate limiting

Basé sur une fenêtre glissante en mémoire (par IP ou par user_id). Configurable via les variables d'environnement :

RATE_LIMIT_LOGIN=10          # tentatives de login / 15 min / IP
RATE_LIMIT_API_READ=1000     # requêtes lecture / heure / user
RATE_LIMIT_API_WRITE=200     # requêtes écriture / heure / user

Claims — contenu du JWT

pub struct Claims {
    pub sub: Uuid,              // user_id
    pub username: String,
    pub roles: Vec<String>,
    pub permissions: Vec<String>,
    pub exp: i64,               // timestamp d'expiration
    pub iat: i64,               // timestamp d'émission
    pub iss: String,            // issuer (app_domain)
    pub jti: Uuid,              // JWT ID unique (pour blacklist)
}

JwtService

Module : rustwarden_core::auth::jwt
Export : prélude

Méthode Signature Description
generate_token (config, user_id, username, roles, permissions) → Result<String> Génère un JWT signé HS256.
validate_token (config, token: &str) → Result<Claims> Valide et décode. Retourne TokenExpired si expiré.
extract_token_from_header (header: &str) → Result<&str> Extrait le token du header Authorization: Bearer <token>.

Trait RustwardenHooks

Interface à implémenter dans votre projet consommateur pour réagir aux événements du framework.

#[async_trait]
pub trait RustwardenHooks: Send + Sync {
    async fn on_user_registered(
        &self,
        db: &DatabaseConnection,
        user_id: Uuid,
        username: &str,
    ) -> anyhow::Result<()> {
        Ok(())  // défaut no-op
    }

    async fn on_user_deleted(
        &self,
        db: &DatabaseConnection,
        user_id: Uuid,
        username: &str,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    async fn on_resource_shared(
        &self,
        db: &DatabaseConnection,
        resource_type: &str,
        resource_id: Uuid,
        shared_with_user_id: Uuid,
        permission_level: &str,
        shared_by_user_id: Uuid,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    async fn on_resource_unshared(
        &self,
        db: &DatabaseConnection,
        resource_type: &str,
        resource_id: Uuid,
        user_id: Uuid,
    ) -> anyhow::Result<()> {
        Ok(())
    }
}

Enregistrement dans main.rs :

let hooks = Arc::new(GitrustHooks::new(repos_base_path.clone()));
let app = RustwardenApp::builder()
    .from_env()
    .hooks(hooks)
    .build()
    .await?;

RBAC API

Vérification des permissions dans un handler

async fn admin_handler(user: AuthUser) -> Result<impl IntoResponse, AppError> {
    if !user.has_role("admin") {
        return Err(AppError::Forbidden);
    }
    // ...
}

Vérification des permissions sur une ressource

use rustwarden_core::prelude::*;

async fn repo_handler(
    State(db): State<DatabaseConnection>,
    user: AuthUser,
    // ... autres extracteurs
) -> Result<impl IntoResponse, AppError> {
    // Vérifier que l'utilisateur peut accéder en lecture
    if !ResourceService::user_can_access(
        &db, user.user_id, "repository", repo.id, "read"
    ).await? {
        return Err(AppError::Forbidden);
    }

    // Déterminer le niveau d'accès effectif
    match ResourceService::effective_permission(
        &db, user.user_id, "repository", repo.id
    ).await? {
        Some(ref p) if p == "owner" => { /* accès total */ }
        Some(ref p) if p == "write" => { /* écriture permise */ }
        Some(ref p) if p == "read"  => { /* lecture seule */ }
        _ => return Err(AppError::Forbidden),
    }
}

i18n — Internationalisation

Module : rustwarden_core::i18n

Gitrust supporte 6 langues : fr, en, de, es, it, pt.

Les fichiers de traduction sont dans crates/rustwarden-core/locales/{lang}.yml et crates/gitrust-web/locales/{lang}.yml.

Dans un template Askama :

<!-- Clé de traduction avec paramètre -->
<h1>{{ t!("repository.title", repo_name = repo.slug) }}</h1>

<!-- Clé simple -->
<button>{{ t!("actions.save") }}</button>

Dans un handler (pour les messages d'erreur i18n) :

use rustwarden_core::i18n::I18n;

let message = I18n::translate(&user_lang, "errors.forbidden", &[]);

La langue est déterminée dans l'ordre : préférences utilisateur (table user_preferences) → cookie lang → header Accept-Language.


Email templating

Module : rustwarden_core::services::email_service

Pour envoyer un email depuis gitrust, utilisez EmailQueueService::enqueue (envoi asynchrone) :

use rustwarden_core::services::email_queue_service::EmailQueueService;

EmailQueueService::enqueue(
    &db,
    &to_email,
    Some(&to_name),
    &from_email,
    Some(&from_name),
    "Votre dépôt a été importé",
    &html_body,
    &text_body,
    "import_success",
).await?;

Les templates d'email sont dans crates/rustwarden-core/templates/emails/ (format HTML + texte brut).


Étendre rustwarden-core

Règle absolue : ne modifiez jamais le code de crates/rustwarden-core/. C'est un framework partagé entre plusieurs projets (gitrust, PasterMan, etc.).

Pour étendre une fonctionnalité :

  1. Wrappers dans gitrust-core : créez un service wrapper dans crates/gitrust-core/src/services/ qui appelle le service rustwarden-core et ajoute la logique spécifique gitrust.
  2. Trait impls dans gitrust-hooks : implémentez RustwardenHooks pour réagir aux événements du framework.
  3. Feature flags : certaines fonctionnalités rustwarden-core sont derrière des features Cargo (oauth, totp, etc.) — activez-les dans Cargo.toml.

Voir aussi