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.
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.