indicateur Rust 2024
EBO <eric.bouhana@softalys.com> committé le 2026-05-09 10:42
17912f969273b41657a718065980e7c14175c8da
1 parent(s)
16 fichiers modifiés
+61
-35
M
Cargo.toml
+4
-0
@@ -7,6 +7,10 @@ members = [
| 7 | 7 | ] |
| 8 | 8 | resolver = "2" |
| 9 | 9 | |
| 10 | +[workspace.package] | |
| 11 | +edition = "2024" | |
| 12 | +rust-version = "1.85" | |
| 13 | + | |
| 10 | 14 | [workspace.lints.clippy] |
| 11 | 15 | empty_line_after_doc_comments = "allow" |
| 12 | 16 |
M
app/Cargo.toml
+2
-1
@@ -1,7 +1,8 @@
| 1 | 1 | [package] |
| 2 | 2 | name = "rustwarden-app" |
| 3 | 3 | version = "0.1.0" |
| 4 | -edition = "2021" | |
| 4 | +edition.workspace = true | |
| 5 | +rust-version.workspace = true | |
| 5 | 6 | description = "Reference application using the Rustwarden framework" |
| 6 | 7 | |
| 7 | 8 | [[bin]] |
M
crates/rustwarden-core/Cargo.toml
+2
-1
@@ -1,7 +1,8 @@
| 1 | 1 | [package] |
| 2 | 2 | name = "rustwarden-core" |
| 3 | 3 | version = "0.1.0" |
| 4 | -edition = "2021" | |
| 4 | +edition.workspace = true | |
| 5 | +rust-version.workspace = true | |
| 5 | 6 | description = "Core library for Rustwarden – authentication, RBAC, user management framework" |
| 6 | 7 | |
| 7 | 8 | [dependencies] |
M
crates/rustwarden-core/src/app.rs
+6
-1
@@ -261,7 +261,12 @@ impl RustwardenBuilder {
| 261 | 261 | db |
| 262 | 262 | } else { |
| 263 | 263 | if let Some(ref url) = self.database_url { |
| 264 | - std::env::set_var("DATABASE_URL", url); | |
| 264 | + // SAFETY: `build()` runs during single-threaded application | |
| 265 | + // initialization, before any worker tasks or background threads | |
| 266 | + // are spawned, so mutating the process environment is sound. | |
| 267 | + unsafe { | |
| 268 | + std::env::set_var("DATABASE_URL", url); | |
| 269 | + } | |
| 265 | 270 | } |
| 266 | 271 | database::create_connection().await? |
| 267 | 272 | }; |
M
crates/rustwarden-core/src/auth/oauth_handlers.rs
+3
-0
@@ -96,6 +96,9 @@ pub async fn oauth_redirect(
| 96 | 96 | /// Handles the OAuth callback from the provider. |
| 97 | 97 | /// Validates state, exchanges code for tokens, finds or creates user, |
| 98 | 98 | /// generates JWT + refresh token, sets cookies, redirects to /home. |
| 99 | +// Axum handler: each argument is a distinct extractor; factoring them into a | |
| 100 | +// combined extractor would obscure the contract for marginal benefit. | |
| 101 | +#[allow(clippy::too_many_arguments)] | |
| 99 | 102 | pub async fn oauth_callback( |
| 100 | 103 | State(db): State<DatabaseConnection>, |
| 101 | 104 | headers: HeaderMap, |
M
crates/rustwarden-core/src/middleware/admin_helper.rs
+1
-1
@@ -53,7 +53,7 @@ pub async fn extract_admin_claims_from_parts(
| 53 | 53 | AppError::Config("Auth configuration not available".to_string()) |
| 54 | 54 | })?; |
| 55 | 55 | |
| 56 | - let claims = JwtService::validate_token(&*auth_config, &jwt_token).map_err(|_| { | |
| 56 | + let claims = JwtService::validate_token(&auth_config, &jwt_token).map_err(|_| { | |
| 57 | 57 | tracing::warn!("Invalid JWT token for admin route"); |
| 58 | 58 | AppError::Unauthorized |
| 59 | 59 | })?; |
M
crates/rustwarden-core/src/middleware/admin_service.rs
+1
-1
@@ -101,7 +101,7 @@ where
| 101 | 101 | } |
| 102 | 102 | }; |
| 103 | 103 | |
| 104 | - let claims = match JwtService::validate_token(&*auth_config, &jwt_token) { | |
| 104 | + let claims = match JwtService::validate_token(&auth_config, &jwt_token) { | |
| 105 | 105 | Ok(claims) => claims, |
| 106 | 106 | Err(_) => { |
| 107 | 107 | let redirect_url = auth_service::sanitize_redirect(uri.path()); |
M
crates/rustwarden-core/src/middleware/auth_required.rs
+3
-3
@@ -70,7 +70,7 @@ async fn extract_user_from_jwt_impl(db: &DatabaseConnection, request: &Request)
| 70 | 70 | } |
| 71 | 71 | |
| 72 | 72 | // Validate JWT token |
| 73 | - match JwtService::validate_token(&*auth_config, &jwt_token) { | |
| 73 | + match JwtService::validate_token(&auth_config, &jwt_token) { | |
| 74 | 74 | Ok(claims) => { |
| 75 | 75 | tracing::debug!("User extracted from JWT: {}", claims.sub); |
| 76 | 76 | Some(claims) |
@@ -150,7 +150,7 @@ pub async fn auth_required_impl(
| 150 | 150 | } |
| 151 | 151 | |
| 152 | 152 | // Validate JWT token |
| 153 | - let claims = match JwtService::validate_token(&*auth_config, &jwt_token) { | |
| 153 | + let claims = match JwtService::validate_token(&auth_config, &jwt_token) { | |
| 154 | 154 | Ok(claims) => claims, |
| 155 | 155 | Err(e) => { |
| 156 | 156 | tracing::warn!("Invalid JWT token for {}: {}", uri, e); |
@@ -224,7 +224,7 @@ pub async fn optional_auth(
| 224 | 224 | .await |
| 225 | 225 | .is_ok_and(|b| !b) |
| 226 | 226 | { |
| 227 | - if let Ok(claims) = JwtService::validate_token(&*auth_config, &jwt_token) { | |
| 227 | + if let Ok(claims) = JwtService::validate_token(&auth_config, &jwt_token) { | |
| 228 | 228 | tracing::debug!("User authenticated: {} (from JWT)", claims.sub); |
| 229 | 229 | } |
| 230 | 230 | } |
M
crates/rustwarden-core/src/middleware/auth_service.rs
+2
-2
@@ -107,7 +107,7 @@ where
| 107 | 107 | } |
| 108 | 108 | }; |
| 109 | 109 | |
| 110 | - let claims = match JwtService::validate_token(&*auth_config, &jwt_token) { | |
| 110 | + let claims = match JwtService::validate_token(&auth_config, &jwt_token) { | |
| 111 | 111 | Ok(claims) => claims, |
| 112 | 112 | Err(_) => { |
| 113 | 113 | tracing::warn!("AuthService: Invalid JWT token, redirecting to login"); |
@@ -232,7 +232,7 @@ where
| 232 | 232 | |
| 233 | 233 | if !is_blacklisted { |
| 234 | 234 | if let Some(auth_config) = parts.extensions.get::<std::sync::Arc<AuthConfig>>().cloned() { |
| 235 | - if let Ok(claims) = JwtService::validate_token(&*auth_config, &jwt_token) | |
| 235 | + if let Ok(claims) = JwtService::validate_token(&auth_config, &jwt_token) | |
| 236 | 236 | { |
| 237 | 237 | parts.extensions.insert(claims.clone()); |
| 238 | 238 | if let Ok(json) = serde_json::to_string(&claims) { |
M
crates/rustwarden-core/src/middleware/error_handler.rs
+4
-7
@@ -141,13 +141,10 @@ impl ErrorCode {
| 141 | 141 | /// |
| 142 | 142 | /// Standardized error response with error code and message |
| 143 | 143 | pub async fn error_handler(request: Request, next: Next) -> Response { |
| 144 | - let response = next.run(request).await; | |
| 145 | - | |
| 146 | - // If response is an error, we can enhance it here | |
| 147 | - // For now, errors are handled by AppError::IntoResponse | |
| 148 | - // This middleware can be extended to add error codes to responses | |
| 149 | - | |
| 150 | - response | |
| 144 | + // Errors are currently handled by `AppError::IntoResponse`. This middleware | |
| 145 | + // is the extension point if we ever want to enrich error responses (codes, | |
| 146 | + // headers, telemetry) without modifying every handler. | |
| 147 | + next.run(request).await | |
| 151 | 148 | } |
| 152 | 149 | |
| 153 | 150 | #[cfg(test)] |
M
crates/rustwarden-core/src/services/crypto_service.rs
+9
-4
@@ -101,10 +101,15 @@ mod tests {
| 101 | 101 | |
| 102 | 102 | #[test] |
| 103 | 103 | fn test_roundtrip() { |
| 104 | - env::set_var( | |
| 105 | - "OAUTH_ENCRYPTION_KEY", | |
| 106 | - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | |
| 107 | - ); | |
| 104 | + // SAFETY: test fixture writing a stable constant key; concurrent tests | |
| 105 | + // in this binary either set the same value or do not read this var, | |
| 106 | + // so the race is benign in practice. | |
| 107 | + unsafe { | |
| 108 | + env::set_var( | |
| 109 | + "OAUTH_ENCRYPTION_KEY", | |
| 110 | + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | |
| 111 | + ); | |
| 112 | + } | |
| 108 | 113 | let secret = "my-super-secret-client-key"; |
| 109 | 114 | let encrypted = CryptoService::encrypt(secret).unwrap(); |
| 110 | 115 | assert!(encrypted.starts_with("enc:")); |
M
crates/rustwarden-core/src/services/oauth/service.rs
+2
-3
@@ -177,10 +177,9 @@ impl OAuthService {
| 177 | 177 | UserService::assign_role_to_user(db, user.id, role.id).await?; |
| 178 | 178 | } |
| 179 | 179 | |
| 180 | - let refreshed = UserService::get_user_by_id(db, user.id) | |
| 180 | + UserService::get_user_by_id(db, user.id) | |
| 181 | 181 | .await? |
| 182 | - .ok_or_else(|| AppError::Internal("User not found after creation".to_string()))?; | |
| 183 | - refreshed | |
| 182 | + .ok_or_else(|| AppError::Internal("User not found after creation".to_string()))? | |
| 184 | 183 | }; |
| 185 | 184 | |
| 186 | 185 | // 4. Link OAuth account to user |
M
crates/rustwarden-core/src/services/totp_service.rs
+16
-8
@@ -476,10 +476,14 @@ mod tests {
| 476 | 476 | |
| 477 | 477 | #[test] |
| 478 | 478 | fn test_generate_backup_codes() { |
| 479 | - std::env::set_var( | |
| 480 | - "ENCRYPTION_KEY", | |
| 481 | - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | |
| 482 | - ); | |
| 479 | + // SAFETY: test fixture writing a stable constant key; concurrent tests | |
| 480 | + // in this binary either set the same value or do not read this var. | |
| 481 | + unsafe { | |
| 482 | + std::env::set_var( | |
| 483 | + "ENCRYPTION_KEY", | |
| 484 | + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | |
| 485 | + ); | |
| 486 | + } | |
| 483 | 487 | |
| 484 | 488 | let (plaintext, encrypted) = TotpService::generate_backup_codes_internal().unwrap(); |
| 485 | 489 |
@@ -504,10 +508,14 @@ mod tests {
| 504 | 508 | |
| 505 | 509 | #[test] |
| 506 | 510 | fn test_backup_codes_are_unique() { |
| 507 | - std::env::set_var( | |
| 508 | - "ENCRYPTION_KEY", | |
| 509 | - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | |
| 510 | - ); | |
| 511 | + // SAFETY: test fixture writing a stable constant key; concurrent tests | |
| 512 | + // in this binary either set the same value or do not read this var. | |
| 513 | + unsafe { | |
| 514 | + std::env::set_var( | |
| 515 | + "ENCRYPTION_KEY", | |
| 516 | + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | |
| 517 | + ); | |
| 518 | + } | |
| 511 | 519 | |
| 512 | 520 | let (codes, _) = TotpService::generate_backup_codes_internal().unwrap(); |
| 513 | 521 | let mut unique = std::collections::HashSet::new(); |
M
crates/rustwarden-ui/Cargo.toml
+2
-1
@@ -1,7 +1,8 @@
| 1 | 1 | [package] |
| 2 | 2 | name = "rustwarden-ui" |
| 3 | 3 | version = "0.1.0" |
| 4 | -edition = "2021" | |
| 4 | +edition.workspace = true | |
| 5 | +rust-version.workspace = true | |
| 5 | 6 | description = "Default UI for Rustwarden – HTML templates, handlers, admin panel" |
| 6 | 7 | |
| 7 | 8 | [dependencies] |
M
template/Cargo.toml
+2
-1
@@ -1,7 +1,8 @@
| 1 | 1 | [package] |
| 2 | 2 | name = "{{project-name}}" |
| 3 | 3 | version = "0.1.0" |
| 4 | -edition = "2021" | |
| 4 | +edition = "2024" | |
| 5 | +rust-version = "1.85" | |
| 5 | 6 | |
| 6 | 7 | [[bin]] |
| 7 | 8 | name = "{{project-name}}" |
M
tools/rustwarden-cli/Cargo.toml
+2
-1
@@ -1,7 +1,8 @@
| 1 | 1 | [package] |
| 2 | 2 | name = "rustwarden-cli" |
| 3 | 3 | version = "0.1.0" |
| 4 | -edition = "2021" | |
| 4 | +edition.workspace = true | |
| 5 | +rust-version.workspace = true | |
| 5 | 6 | description = "CLI wizard to scaffold new Rustwarden applications" |
| 6 | 7 | |
| 7 | 8 | [[bin]] |
GitRust