ajout de methodes
EBO <eric.bouhana@softalys.com> committé le 2026-03-26 16:10
52aa055d9a661cb9063a1646ef9a6f1b66f73547
1 parent(s)
5 fichiers modifiés
+337
-43
M
crates/rustwarden-core/src/hooks.rs
+14
-0
@@ -111,6 +111,20 @@ pub trait RustwardenHooks: Send + Sync {
| 111 | 111 | Ok(()) |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | + /// Called before a user is deleted. | |
| 115 | + /// | |
| 116 | + /// Use this hook to clean up business data owned by the user | |
| 117 | + /// (walls, repos, workspaces, etc.) before the cascade delete | |
| 118 | + /// removes framework-managed data. | |
| 119 | + async fn on_user_deleted( | |
| 120 | + &self, | |
| 121 | + _db: &DatabaseConnection, | |
| 122 | + _user_id: Uuid, | |
| 123 | + _username: &str, | |
| 124 | + ) -> anyhow::Result<()> { | |
| 125 | + Ok(()) | |
| 126 | + } | |
| 127 | + | |
| 114 | 128 | /// Called after a successful OAuth login (new or existing user). |
| 115 | 129 | async fn on_oauth_login( |
| 116 | 130 | &self, |
M
crates/rustwarden-core/src/services/user_service.rs
+171
-1
@@ -1,8 +1,11 @@
| 1 | 1 | use crate::error::AppError; |
| 2 | 2 | use crate::models::{role, user, user_role}; |
| 3 | +use crate::services::password_service::PasswordService; | |
| 4 | +use crate::services::refresh_token_service::RefreshTokenService; | |
| 3 | 5 | use chrono::Utc; |
| 4 | 6 | use sea_orm::{ |
| 5 | - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, NotSet, QueryFilter, Set, | |
| 7 | + ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, NotSet, | |
| 8 | + QueryFilter, Set, | |
| 6 | 9 | }; |
| 7 | 10 | use uuid::Uuid; |
| 8 | 11 |
@@ -305,4 +308,171 @@ impl UserService {
| 305 | 308 | |
| 306 | 309 | Ok(()) |
| 307 | 310 | } |
| 311 | + | |
| 312 | + /// Remove a role from a user. | |
| 313 | + /// | |
| 314 | + /// Prevents removing the `admin` role from the last remaining admin | |
| 315 | + /// to avoid locking out the application. | |
| 316 | + /// | |
| 317 | + /// # Errors | |
| 318 | + /// | |
| 319 | + /// * `AppError::Validation` if this would remove the last admin | |
| 320 | + pub async fn unassign_role_from_user( | |
| 321 | + db: &DatabaseConnection, | |
| 322 | + user_id: Uuid, | |
| 323 | + role_id: Uuid, | |
| 324 | + ) -> Result<(), AppError> { | |
| 325 | + // Protect last admin | |
| 326 | + let role = role::Entity::find_by_id(role_id) | |
| 327 | + .one(db) | |
| 328 | + .await? | |
| 329 | + .ok_or_else(|| AppError::NotFound("Role not found".to_string()))?; | |
| 330 | + | |
| 331 | + if role.name == "admin" { | |
| 332 | + let admin_count = Self::get_all_admin_users(db).await?.len(); | |
| 333 | + if admin_count <= 1 { | |
| 334 | + return Err(AppError::Validation( | |
| 335 | + "Cannot remove the admin role from the last administrator".to_string(), | |
| 336 | + )); | |
| 337 | + } | |
| 338 | + } | |
| 339 | + | |
| 340 | + user_role::Entity::delete_many() | |
| 341 | + .filter(user_role::Column::UserId.eq(user_id)) | |
| 342 | + .filter(user_role::Column::RoleId.eq(role_id)) | |
| 343 | + .exec(db) | |
| 344 | + .await?; | |
| 345 | + | |
| 346 | + tracing::info!("Role '{}' removed from user {}", role.name, user_id); | |
| 347 | + Ok(()) | |
| 348 | + } | |
| 349 | + | |
| 350 | + /// Delete a user and clean up all related data. | |
| 351 | + /// | |
| 352 | + /// This method: | |
| 353 | + /// 1. Calls the `on_user_deleted` hook (so consumers can clean up business data) | |
| 354 | + /// 2. Revokes all refresh tokens for the user | |
| 355 | + /// 3. Deletes the user (cascade handles user_roles, resources, shares, oauth_accounts) | |
| 356 | + /// | |
| 357 | + /// # Errors | |
| 358 | + /// | |
| 359 | + /// * `AppError::NotFound` if user does not exist | |
| 360 | + /// * `AppError::Validation` if user is the last admin | |
| 361 | + pub async fn delete_user( | |
| 362 | + db: &DatabaseConnection, | |
| 363 | + user_id: Uuid, | |
| 364 | + hooks: Option<&dyn crate::hooks::RustwardenHooks>, | |
| 365 | + ) -> Result<(), AppError> { | |
| 366 | + let user = Self::get_user_by_id(db, user_id) | |
| 367 | + .await? | |
| 368 | + .ok_or_else(|| AppError::NotFound("User not found".to_string()))?; | |
| 369 | + | |
| 370 | + // Protect last admin | |
| 371 | + let roles = Self::get_user_roles(db, user_id).await?; | |
| 372 | + if roles.contains(&"admin".to_string()) { | |
| 373 | + let admin_count = Self::get_all_admin_users(db).await?.len(); | |
| 374 | + if admin_count <= 1 { | |
| 375 | + return Err(AppError::Validation( | |
| 376 | + "Cannot delete the last administrator".to_string(), | |
| 377 | + )); | |
| 378 | + } | |
| 379 | + } | |
| 380 | + | |
| 381 | + // Hook: let consumers clean up their business data before cascade | |
| 382 | + if let Some(hooks) = hooks { | |
| 383 | + if let Err(e) = hooks.on_user_deleted(db, user_id, &user.username).await { | |
| 384 | + tracing::warn!("on_user_deleted hook error for user {}: {}", user_id, e); | |
| 385 | + } | |
| 386 | + } | |
| 387 | + | |
| 388 | + // Revoke all refresh tokens (invalidates active sessions) | |
| 389 | + RefreshTokenService::revoke_all_tokens_for_user(db, user_id).await?; | |
| 390 | + | |
| 391 | + // Delete user (cascade handles user_roles, resources, shares, oauth_accounts) | |
| 392 | + user.delete(db).await?; | |
| 393 | + | |
| 394 | + tracing::info!("User {} deleted", user_id); | |
| 395 | + Ok(()) | |
| 396 | + } | |
| 397 | + | |
| 398 | + /// Admin password reset: set a new password directly without email confirmation. | |
| 399 | + /// | |
| 400 | + /// Sets `must_change_password = true` so the user is forced to choose | |
| 401 | + /// their own password at next login. Revokes all existing sessions (SEC-M7). | |
| 402 | + /// | |
| 403 | + /// # Arguments | |
| 404 | + /// | |
| 405 | + /// * `db` - Database connection | |
| 406 | + /// * `user_id` - Target user ID | |
| 407 | + /// * `new_password` - New plaintext password (will be hashed) | |
| 408 | + pub async fn admin_reset_password( | |
| 409 | + db: &DatabaseConnection, | |
| 410 | + user_id: Uuid, | |
| 411 | + new_password: &str, | |
| 412 | + ) -> Result<(), AppError> { | |
| 413 | + let hash = PasswordService::hash_password(new_password).await?; | |
| 414 | + let user = Self::get_user_by_id(db, user_id) | |
| 415 | + .await? | |
| 416 | + .ok_or_else(|| AppError::NotFound("User not found".to_string()))?; | |
| 417 | + | |
| 418 | + let mut active: user::ActiveModel = user.into(); | |
| 419 | + active.password_hash = Set(hash); | |
| 420 | + active.must_change_password = Set(true); | |
| 421 | + active.password_changed_at = Set(Some(Utc::now())); | |
| 422 | + active.updated_at = Set(Utc::now()); | |
| 423 | + active.update(db).await?; | |
| 424 | + | |
| 425 | + // SEC-M7: invalidate all sessions | |
| 426 | + RefreshTokenService::revoke_all_tokens_for_user(db, user_id).await?; | |
| 427 | + | |
| 428 | + tracing::info!("Admin reset password for user {}", user_id); | |
| 429 | + Ok(()) | |
| 430 | + } | |
| 431 | + | |
| 432 | + /// Change password with old password verification (for authenticated users). | |
| 433 | + /// | |
| 434 | + /// Simpler than `PasswordChangeService` (no email confirmation required). | |
| 435 | + /// Validates password complexity, verifies old password, hashes new password, | |
| 436 | + /// and revokes all sessions (SEC-M7). | |
| 437 | + /// | |
| 438 | + /// # Arguments | |
| 439 | + /// | |
| 440 | + /// * `db` - Database connection | |
| 441 | + /// * `user_id` - Authenticated user ID | |
| 442 | + /// * `old_password` - Current password for verification | |
| 443 | + /// * `new_password` - New plaintext password | |
| 444 | + pub async fn change_password_direct( | |
| 445 | + db: &DatabaseConnection, | |
| 446 | + user_id: Uuid, | |
| 447 | + old_password: &str, | |
| 448 | + new_password: &str, | |
| 449 | + ) -> Result<(), AppError> { | |
| 450 | + let user = Self::get_user_by_id(db, user_id) | |
| 451 | + .await? | |
| 452 | + .ok_or_else(|| AppError::NotFound("User not found".to_string()))?; | |
| 453 | + | |
| 454 | + if !PasswordService::verify_password(old_password, &user.password_hash).await? { | |
| 455 | + return Err(AppError::Validation( | |
| 456 | + "Current password is incorrect".to_string(), | |
| 457 | + )); | |
| 458 | + } | |
| 459 | + | |
| 460 | + crate::middleware::password_complexity::validate_password_complexity(db, new_password) | |
| 461 | + .await?; | |
| 462 | + | |
| 463 | + let hash = PasswordService::hash_password(new_password).await?; | |
| 464 | + | |
| 465 | + let mut active: user::ActiveModel = user.into(); | |
| 466 | + active.password_hash = Set(hash); | |
| 467 | + active.must_change_password = Set(false); | |
| 468 | + active.password_changed_at = Set(Some(Utc::now())); | |
| 469 | + active.updated_at = Set(Utc::now()); | |
| 470 | + active.update(db).await?; | |
| 471 | + | |
| 472 | + // SEC-M7: invalidate all sessions | |
| 473 | + RefreshTokenService::revoke_all_tokens_for_user(db, user_id).await?; | |
| 474 | + | |
| 475 | + tracing::info!("Password changed directly for user {}", user_id); | |
| 476 | + Ok(()) | |
| 477 | + } | |
| 308 | 478 | } |
M
docs/MIGRATION_GUIDE.md
+56
-0
@@ -7,6 +7,62 @@ Les entrees sont classees par date, de la plus recente a la plus ancienne.
| 7 | 7 | |
| 8 | 8 | --- |
| 9 | 9 | |
| 10 | +## 2026-03-26 — `UserService` : nouvelles methodes + hook `on_user_deleted` | |
| 11 | + | |
| 12 | +### Changement | |
| 13 | + | |
| 14 | +4 methodes ajoutees a `UserService` et 1 hook au trait `RustwardenHooks` : | |
| 15 | + | |
| 16 | +| Methode | Description | | |
| 17 | +|---------|-------------| | |
| 18 | +| `unassign_role_from_user(db, user_id, role_id)` | Retire un role. Protege le dernier admin. | | |
| 19 | +| `delete_user(db, user_id, hooks)` | Supprime un utilisateur avec nettoyage complet. | | |
| 20 | +| `admin_reset_password(db, user_id, new_password)` | Reset admin sans confirmation email. Force `must_change_password`. | | |
| 21 | +| `change_password_direct(db, user_id, old, new)` | Changement direct avec verification de l'ancien mot de passe. | | |
| 22 | + | |
| 23 | +**Hook ajoute** : `on_user_deleted(db, user_id, username)` — appele avant la suppression | |
| 24 | +pour permettre le nettoyage des donnees metier (walls, repos, etc.). | |
| 25 | + | |
| 26 | +### Migration | |
| 27 | + | |
| 28 | +**Aucune migration requise.** Toutes les methodes sont additives. Le hook a une | |
| 29 | +implementation par defaut (no-op). | |
| 30 | + | |
| 31 | +### Remplacement des contournements | |
| 32 | + | |
| 33 | +Si votre projet manipulait directement les entites SeaORM pour ces operations, | |
| 34 | +vous pouvez maintenant utiliser les methodes du framework : | |
| 35 | + | |
| 36 | +```rust | |
| 37 | +// AVANT — suppression manuelle | |
| 38 | +user::Entity::delete_by_id(user_id).exec(&db).await?; | |
| 39 | + | |
| 40 | +// APRES — nettoyage complet (tokens, hook, cascade) | |
| 41 | +UserService::delete_user(&db, user_id, Some(&my_hooks)).await?; | |
| 42 | +``` | |
| 43 | + | |
| 44 | +```rust | |
| 45 | +// AVANT — retrait de role manuel | |
| 46 | +user_role::Entity::delete_many() | |
| 47 | + .filter(user_role::Column::UserId.eq(user_id)) | |
| 48 | + .filter(user_role::Column::RoleId.eq(role_id)) | |
| 49 | + .exec(&db).await?; | |
| 50 | + | |
| 51 | +// APRES — avec protection du dernier admin | |
| 52 | +UserService::unassign_role_from_user(&db, user_id, role_id).await?; | |
| 53 | +``` | |
| 54 | + | |
| 55 | +```rust | |
| 56 | +// AVANT — reset admin manuel | |
| 57 | +let hash = PasswordService::hash_password(new_pwd).await?; | |
| 58 | +UserService::update_password_hash(&db, user_id, &hash).await?; | |
| 59 | + | |
| 60 | +// APRES — hash + must_change + revocation sessions | |
| 61 | +UserService::admin_reset_password(&db, user_id, new_pwd).await?; | |
| 62 | +``` | |
| 63 | + | |
| 64 | +--- | |
| 65 | + | |
| 10 | 66 | ## 2026-03-26 — `PasswordService` : methodes rendues async |
| 11 | 67 | |
| 12 | 68 | ### Changement |
M
docs/SERVICES_REFERENCE.md
+5
-1
@@ -112,8 +112,12 @@ Gestion CRUD des utilisateurs et de leurs roles.
| 112 | 112 | | `get_all_admin_users` | `(db) → Result<Vec<user::Model>>` | Liste les utilisateurs avec le role `admin`. | |
| 113 | 113 | | `get_user_roles` | `(db, user_id: Uuid) → Result<Vec<String>>` | Retourne les noms des roles d'un utilisateur (ex: `["admin", "user"]`). | |
| 114 | 114 | | `assign_role_to_user` | `(db, user_id: Uuid, role_id: Uuid) → Result<user_role::Model>` | Assigne un role a un utilisateur. | |
| 115 | +| `unassign_role_from_user` | `(db, user_id: Uuid, role_id: Uuid) → Result<()>` | Retire un role a un utilisateur. Empeche de retirer le role `admin` au dernier administrateur. | | |
| 115 | 116 | | `update_profile` | `(db, user_id: Uuid, username: Option<String>, email: Option<String>) → Result<user::Model>` | Met a jour le profil. Valide l'unicite du username et de l'email. | |
| 116 | -| `change_password` | `(db, user_id: Uuid, old_password: &str, new_password: &str, current_token_id: Option<Uuid>) → Result<(Uuid, String)>` | Initie un changement de mot de passe (cree une demande pendante). Retourne `(request_id, plain_token)` pour confirmation. | | |
| 117 | +| `change_password` | `(db, user_id: Uuid, old_password: &str, new_password: &str, current_token_id: Option<Uuid>) → Result<(Uuid, String)>` | Initie un changement de mot de passe avec confirmation email (cree une demande pendante). Retourne `(request_id, plain_token)`. | | |
| 118 | +| `change_password_direct` | `(db, user_id: Uuid, old_password: &str, new_password: &str) → Result<()>` | Change le mot de passe directement (sans confirmation email). Verifie l'ancien mot de passe, valide la complexite, revoque les sessions (SEC-M7). | | |
| 119 | +| `admin_reset_password` | `(db, user_id: Uuid, new_password: &str) → Result<()>` | Reset admin : hash et applique le nouveau mot de passe. Force `must_change_password = true`. Revoque les sessions (SEC-M7). | | |
| 120 | +| `delete_user` | `(db, user_id: Uuid, hooks: Option<&dyn RustwardenHooks>) → Result<()>` | Supprime un utilisateur. Appelle le hook `on_user_deleted`, revoque les tokens, cascade DB. Empeche de supprimer le dernier admin. | | |
| 117 | 121 | | `update_password_hash` | `(db, user_id: Uuid, password_hash: &str) → Result<()>` | Met a jour directement le hash — usage interne (PasswordChangeService). | |
| 118 | 122 | |
| 119 | 123 | #### Modele `user::Model` |
M
docs/UPSTREAM_RUSTWARDEN.md
+91
-41
@@ -1,62 +1,112 @@
| 1 | 1 | # Notes d'amelioration pour RustWarden-core |
| 2 | 2 | |
| 3 | -## 1. `PasswordService` : operations bcrypt bloquantes dans un contexte async | |
| 3 | +## 1. `UserService::delete_user` — IMPLEMENTE | |
| 4 | 4 | |
| 5 | -**Statut : IMPLEMENTE** (approche 2 — methodes rendues directement async) | |
| 5 | +**Statut : IMPLEMENTE** | |
| 6 | 6 | |
| 7 | -**Probleme :** `PasswordService::hash_password` et `PasswordService::verify_password` etaient des fonctions synchrones executant bcrypt (CPU-intensif, ~100-300ms selon le cost factor). Elles bloquaient le thread du runtime Tokio. | |
| 7 | +Methode ajoutee avec : | |
| 8 | +- Protection contre la suppression du dernier admin | |
| 9 | +- Hook `on_user_deleted` appele avant la suppression (permet aux consommateurs de nettoyer leurs donnees metier) | |
| 10 | +- Revocation de tous les refresh tokens | |
| 11 | +- Cascade DB pour user_roles, resources, shares, oauth_accounts | |
| 8 | 12 | |
| 9 | -**Solution implementee :** Les methodes ont ete rendues `async` avec `tokio::task::spawn_blocking` interne. Aucun suffixe `_async`, aucune duplication d'API. Tous les call sites (deja en contexte async) ont ete migres mecaniquement avec `.await`. | |
| 13 | +```rust | |
| 14 | +pub async fn delete_user( | |
| 15 | + db: &DatabaseConnection, | |
| 16 | + user_id: Uuid, | |
| 17 | + hooks: Option<&dyn RustwardenHooks>, | |
| 18 | +) -> Result<(), AppError> | |
| 19 | +``` | |
| 10 | 20 | |
| 11 | -**Approches evaluees et rejetees :** | |
| 12 | -- **Suffixe `_async`** (ex: `hash_password_async`) — rejete car duplication d'API et anti-pattern en Rust | |
| 13 | -- **Trait polymorphe** (`PasswordHasher`) — rejete car over-engineering (un seul algorithme utilise, pas de besoin de substitution) | |
| 21 | +Le parametre `hooks` est optionnel (`None` si pas de nettoyage metier necessaire). | |
| 14 | 22 | |
| 15 | -**Fichiers modifies :** | |
| 23 | +--- | |
| 16 | 24 | |
| 17 | -| Fichier | Modification | | |
| 18 | -|---------|-------------| | |
| 19 | -| `services/password_service.rs` | `hash_password` et `verify_password` rendues `async` avec `spawn_blocking` | | |
| 20 | -| `services/auth_service.rs` | `.await` sur `verify_password` + dummy hash migre vers `PasswordService::verify_password` | | |
| 21 | -| `services/user_service.rs` | `.await` sur `verify_password` | | |
| 22 | -| `services/password_change_service.rs` | `.await` sur `hash_password` | | |
| 23 | -| `services/password_reset_service.rs` | `.await` sur `hash_password` | | |
| 24 | -| `services/default_admin_service.rs` | `.await` sur `hash_password` | | |
| 25 | -| `auth/handlers.rs` | `.await` sur `hash_password` (handler register) | | |
| 26 | -| `bin/seed.rs` | `.await` sur `hash_password` (deja `#[tokio::main]`) | | |
| 27 | -| `bin/change_password.rs` | `.await` sur `hash_password` (deja `#[tokio::main]`) | | |
| 28 | -| `rustwarden-ui: admin_handler.rs` | `.await` sur `hash_password` (2 call sites) | | |
| 29 | -| `rustwarden-ui: settings_handler.rs` | `.await` sur `verify_password` et `hash_password` | | |
| 30 | -| `rustwarden-ui: user_handler.rs` | `.await` sur `hash_password` (2 call sites) | | |
| 25 | +## 2. `UserService::unassign_role_from_user` — IMPLEMENTE | |
| 31 | 26 | |
| 32 | -**Note :** `bin/hash_password.rs` n'est pas impacte — il appelle `bcrypt::hash` directement (CLI sync sans runtime Tokio). | |
| 27 | +**Statut : IMPLEMENTE** | |
| 33 | 28 | |
| 34 | -**Impact sur les consommateurs :** Breaking change mineur — tout appel a `PasswordService::hash_password()` ou `verify_password()` doit ajouter `.await`. Comme tous les contextes d'appel sont deja async, la migration est mecanique. | |
| 29 | +Methode ajoutee avec protection contre le retrait du role admin au dernier administrateur. | |
| 30 | + | |
| 31 | +```rust | |
| 32 | +pub async fn unassign_role_from_user( | |
| 33 | + db: &DatabaseConnection, | |
| 34 | + user_id: Uuid, | |
| 35 | + role_id: Uuid, | |
| 36 | +) -> Result<(), AppError> | |
| 37 | +``` | |
| 35 | 38 | |
| 36 | 39 | --- |
| 37 | 40 | |
| 38 | -## 2. `UserService::create_user` : demande un hash pre-calcule | |
| 41 | +## 3. `UserService::admin_reset_password` — IMPLEMENTE | |
| 42 | + | |
| 43 | +**Statut : IMPLEMENTE** | |
| 44 | + | |
| 45 | +Methode ajoutee avec : | |
| 46 | +- Hash bcrypt async (via `spawn_blocking`) | |
| 47 | +- `must_change_password = true` force au prochain login | |
| 48 | +- `password_changed_at` mis a jour | |
| 49 | +- SEC-M7 : revocation de tous les refresh tokens | |
| 50 | + | |
| 51 | +```rust | |
| 52 | +pub async fn admin_reset_password( | |
| 53 | + db: &DatabaseConnection, | |
| 54 | + user_id: Uuid, | |
| 55 | + new_password: &str, | |
| 56 | +) -> Result<(), AppError> | |
| 57 | +``` | |
| 58 | + | |
| 59 | +--- | |
| 39 | 60 | |
| 40 | -**Statut : A FAIRE** (depend de §1, qui est maintenant implemente) | |
| 61 | +## 4. `UserService::change_password_direct` — IMPLEMENTE | |
| 41 | 62 | |
| 42 | -**Observation :** `create_user(db, username, email, password_hash, must_change_password)` attend un hash deja calcule. L'appelant doit connaitre l'implementation du hashing (bcrypt) et appeler `hash_password().await` lui-meme. | |
| 63 | +**Statut : IMPLEMENTE** | |
| 43 | 64 | |
| 44 | -**Suggestion :** Ajouter une methode de convenance : | |
| 65 | +Methode ajoutee avec : | |
| 66 | +- Verification de l'ancien mot de passe | |
| 67 | +- Validation de complexite via `validate_password_complexity` | |
| 68 | +- Hash bcrypt async | |
| 69 | +- `must_change_password = false`, `password_changed_at` mis a jour | |
| 70 | +- SEC-M7 : revocation de tous les refresh tokens | |
| 45 | 71 | |
| 46 | 72 | ```rust |
| 47 | -impl UserService { | |
| 48 | - /// Create a user from a plaintext password (hashes on a blocking thread). | |
| 49 | - pub async fn create_user_with_password( | |
| 50 | - db: &DatabaseConnection, | |
| 51 | - username: String, | |
| 52 | - email: String, | |
| 53 | - password: &str, | |
| 54 | - must_change_password: bool, | |
| 55 | - ) -> Result<user::Model, AppError> { | |
| 56 | - let hash = PasswordService::hash_password(password).await?; | |
| 57 | - Self::create_user(db, username, email, hash, must_change_password).await | |
| 58 | - } | |
| 73 | +pub async fn change_password_direct( | |
| 74 | + db: &DatabaseConnection, | |
| 75 | + user_id: Uuid, | |
| 76 | + old_password: &str, | |
| 77 | + new_password: &str, | |
| 78 | +) -> Result<(), AppError> | |
| 79 | +``` | |
| 80 | + | |
| 81 | +--- | |
| 82 | + | |
| 83 | +## Hook ajoute : `on_user_deleted` | |
| 84 | + | |
| 85 | +**Trait `RustwardenHooks`** — nouveau hook : | |
| 86 | + | |
| 87 | +```rust | |
| 88 | +async fn on_user_deleted( | |
| 89 | + &self, | |
| 90 | + _db: &DatabaseConnection, | |
| 91 | + _user_id: Uuid, | |
| 92 | + _username: &str, | |
| 93 | +) -> anyhow::Result<()> { | |
| 94 | + Ok(()) | |
| 59 | 95 | } |
| 60 | 96 | ``` |
| 61 | 97 | |
| 62 | -La methode existante reste disponible pour les cas ou le hash est deja calcule (import, migration). | |
| 98 | +Appele par `delete_user` avant la suppression en base. Permet aux consommateurs de | |
| 99 | +nettoyer leurs donnees metier (walls, repos, workspaces, etc.). | |
| 100 | + | |
| 101 | +--- | |
| 102 | + | |
| 103 | +## Contournements obsoletes dans les projets consommateurs | |
| 104 | + | |
| 105 | +Les contournements suivants peuvent etre remplaces par les methodes du framework : | |
| 106 | + | |
| 107 | +| Contournement | Remplacer par | | |
| 108 | +|--------------|---------------| | |
| 109 | +| `Entity::delete_by_id()` direct | `UserService::delete_user(db, user_id, Some(&hooks))` | | |
| 110 | +| `user_role::Entity::delete_many()` direct | `UserService::unassign_role_from_user(db, user_id, role_id)` | | |
| 111 | +| `hash_password()` + `update_password_hash()` manuels | `UserService::admin_reset_password(db, user_id, new_password)` | | |
| 112 | +| `verify_password()` + `hash_password()` + `update_password_hash()` | `UserService::change_password_direct(db, user_id, old, new)` | |
GitRust