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>ouAuthorization: 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é :
- 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. - Trait impls dans gitrust-hooks : implémentez
RustwardenHookspour réagir aux événements du framework. - Feature flags : certaines fonctionnalités rustwarden-core sont derrière des features Cargo (
oauth,totp, etc.) — activez-les dansCargo.toml.
GitRust