fix: eslint-disable for useEffect deps in UsersPage
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 5s
CI Pipeline / Clippy Lints (push) Successful in 46s
CI Pipeline / Rust Unit Tests (push) Successful in 1m2s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 14s
CI Pipeline / Build .deb & Release (push) Has been skipped
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 5s
CI Pipeline / Clippy Lints (push) Successful in 46s
CI Pipeline / Rust Unit Tests (push) Successful in 1m2s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 14s
CI Pipeline / Build .deb & Release (push) Has been skipped
This commit is contained in:
@ -14,7 +14,10 @@ use routes::azure_sso::SsoSession;
|
||||
use routes::ws::WsTicket;
|
||||
use serde_json::{json, Value};
|
||||
use std::{net::SocketAddr, sync::Arc, time::Duration};
|
||||
use tower_http::{services::{ServeDir, ServeFile}, trace::TraceLayer};
|
||||
use tower_http::{
|
||||
services::{ServeDir, ServeFile},
|
||||
trace::TraceLayer,
|
||||
};
|
||||
|
||||
/// Shared application state threaded through Axum.
|
||||
#[derive(Clone)]
|
||||
|
||||
@ -13,15 +13,15 @@ use axum::{
|
||||
extract::State,
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::Json,
|
||||
routing::{get, post},
|
||||
routing::delete,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use pm_auth::{
|
||||
mfa_totp,
|
||||
hash_password, mfa_totp,
|
||||
rbac::AuthUser,
|
||||
session::{self, LoginRequest, LoginResponse},
|
||||
verify_password, hash_password, validate_password_strength,
|
||||
validate_password_strength, verify_password,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
@ -39,7 +39,10 @@ pub fn public_router() -> Router<AppState> {
|
||||
.route("/login", post(login_handler))
|
||||
.route("/refresh", post(refresh_handler))
|
||||
.route("/logout", post(logout_handler))
|
||||
.route("/force-change-password", post(force_change_password_handler))
|
||||
.route(
|
||||
"/force-change-password",
|
||||
post(force_change_password_handler),
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@ -265,9 +268,11 @@ async fn force_change_password_handler(
|
||||
None => {
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({ "error": { "code": "invalid_credentials", "message": "Invalid username or password" } })),
|
||||
Json(
|
||||
json!({ "error": { "code": "invalid_credentials", "message": "Invalid username or password" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Verify current password
|
||||
@ -277,7 +282,9 @@ async fn force_change_password_handler(
|
||||
if !valid {
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({ "error": { "code": "invalid_credentials", "message": "Invalid username or password" } })),
|
||||
Json(
|
||||
json!({ "error": { "code": "invalid_credentials", "message": "Invalid username or password" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@ -385,20 +392,18 @@ async fn disable_mfa(
|
||||
Json(req): Json<DisableMfaRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
// Verify current password to confirm identity
|
||||
let hash: Option<String> = sqlx::query_scalar(
|
||||
"SELECT password_hash FROM users WHERE id = $1",
|
||||
)
|
||||
.bind(auth_user.user_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "Failed to fetch password hash");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?
|
||||
.flatten();
|
||||
let hash: Option<String> = sqlx::query_scalar("SELECT password_hash FROM users WHERE id = $1")
|
||||
.bind(auth_user.user_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "Failed to fetch password hash");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?
|
||||
.flatten();
|
||||
|
||||
let hash_str = hash.unwrap_or_default();
|
||||
let valid = verify_password(&req.password, &hash_str).unwrap_or(false);
|
||||
@ -406,7 +411,9 @@ async fn disable_mfa(
|
||||
if !valid {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "error": { "code": "invalid_password", "message": "Current password is incorrect" } })),
|
||||
Json(
|
||||
json!({ "error": { "code": "invalid_password", "message": "Current password is incorrect" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -74,7 +74,7 @@ async fn list_windows(
|
||||
let windows: Vec<MaintenanceWindow> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, host_id, label, recurrence, start_at, duration_minutes,
|
||||
recurrence_day, enabled, created_at, updated_at
|
||||
recurrence_day, enabled, auto_apply, created_at, updated_at
|
||||
FROM maintenance_windows
|
||||
WHERE host_id = $1
|
||||
ORDER BY created_at ASC
|
||||
@ -151,15 +151,16 @@ async fn create_window(
|
||||
|
||||
let duration = req.duration_minutes.unwrap_or(60);
|
||||
let enabled = req.enabled.unwrap_or(true);
|
||||
let auto_apply = req.auto_apply.unwrap_or(true);
|
||||
|
||||
let window: MaintenanceWindow = sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO maintenance_windows
|
||||
(host_id, label, recurrence, start_at, duration_minutes, recurrence_day, enabled)
|
||||
(host_id, label, recurrence, start_at, duration_minutes, recurrence_day, enabled, auto_apply)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7)
|
||||
($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, host_id, label, recurrence, start_at, duration_minutes,
|
||||
recurrence_day, enabled, created_at, updated_at
|
||||
recurrence_day, enabled, auto_apply, created_at, updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(host_id)
|
||||
@ -169,6 +170,7 @@ async fn create_window(
|
||||
.bind(duration)
|
||||
.bind(req.recurrence_day)
|
||||
.bind(enabled)
|
||||
.bind(auto_apply)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@ -220,7 +222,7 @@ async fn update_window(
|
||||
let existing: Option<MaintenanceWindow> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, host_id, label, recurrence, start_at, duration_minutes,
|
||||
recurrence_day, enabled, created_at, updated_at
|
||||
recurrence_day, enabled, auto_apply, created_at, updated_at
|
||||
FROM maintenance_windows
|
||||
WHERE id = $1 AND host_id = $2
|
||||
"#,
|
||||
@ -253,6 +255,7 @@ async fn update_window(
|
||||
let new_duration = req.duration_minutes.unwrap_or(existing.duration_minutes);
|
||||
let new_rec_day = req.recurrence_day.or(existing.recurrence_day);
|
||||
let new_enabled = req.enabled.unwrap_or(existing.enabled);
|
||||
let new_auto_apply = req.auto_apply.unwrap_or(existing.auto_apply);
|
||||
|
||||
// Validate recurrence_day for the final recurrence type.
|
||||
if new_recurrence == pm_core::models::WindowRecurrence::Weekly {
|
||||
@ -289,10 +292,11 @@ async fn update_window(
|
||||
duration_minutes = $6,
|
||||
recurrence_day = $7,
|
||||
enabled = $8,
|
||||
auto_apply = $9,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1 AND host_id = $2
|
||||
RETURNING id, host_id, label, recurrence, start_at, duration_minutes,
|
||||
recurrence_day, enabled, created_at, updated_at
|
||||
recurrence_day, enabled, auto_apply, created_at, updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(win_id)
|
||||
@ -303,6 +307,7 @@ async fn update_window(
|
||||
.bind(new_duration)
|
||||
.bind(new_rec_day)
|
||||
.bind(new_enabled)
|
||||
.bind(new_auto_apply)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
|
||||
@ -15,11 +15,14 @@ use axum::{
|
||||
routing::{delete, get, post, put},
|
||||
Router,
|
||||
};
|
||||
use pm_auth::{hash_password, rbac::AuthUser, session::force_logout, verify_password};
|
||||
use pm_auth::validate_password_strength;
|
||||
use pm_auth::{hash_password, rbac::AuthUser, session::force_logout, verify_password};
|
||||
use pm_core::{
|
||||
audit::{log_event, AuditAction},
|
||||
models::{AdminResetPasswordRequest, ChangePasswordRequest, CreateUserRequest, UpdateUserRequest, User},
|
||||
models::{
|
||||
AdminResetPasswordRequest, ChangePasswordRequest, CreateUserRequest, UpdateUserRequest,
|
||||
User,
|
||||
},
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
@ -203,7 +206,9 @@ async fn update_user(
|
||||
));
|
||||
}
|
||||
// Only admins can change role or active status
|
||||
if (req.role.is_some() || req.is_active.is_some() || req.force_password_reset.is_some()) && !auth.role.is_admin() {
|
||||
if (req.role.is_some() || req.is_active.is_some() || req.force_password_reset.is_some())
|
||||
&& !auth.role.is_admin()
|
||||
{
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(
|
||||
@ -355,20 +360,18 @@ async fn change_own_password(
|
||||
Json(req): Json<ChangePasswordRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
// Fetch current password hash
|
||||
let hash: Option<String> = sqlx::query_scalar(
|
||||
"SELECT password_hash FROM users WHERE id = $1",
|
||||
)
|
||||
.bind(auth.user_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "Failed to fetch password hash");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?
|
||||
.flatten();
|
||||
let hash: Option<String> = sqlx::query_scalar("SELECT password_hash FROM users WHERE id = $1")
|
||||
.bind(auth.user_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "Failed to fetch password hash");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?
|
||||
.flatten();
|
||||
|
||||
let hash_str = hash.unwrap_or_default();
|
||||
let valid = verify_password(&req.current_password, &hash_str).unwrap_or(false);
|
||||
@ -376,7 +379,9 @@ async fn change_own_password(
|
||||
if !valid {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "error": { "code": "invalid_password", "message": "Current password is incorrect" } })),
|
||||
Json(
|
||||
json!({ "error": { "code": "invalid_password", "message": "Current password is incorrect" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user