feat: add reporter role for SSO auto-provisioning
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 5s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m10s
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 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m10s
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:
@ -104,11 +104,11 @@ fn pem_response(pem: String, filename: &str) -> Result<Response<Body>, (StatusCo
|
||||
|
||||
// ── Helper: admin-only guard ──────────────────────────────────────────────────
|
||||
|
||||
fn require_admin(user: &AuthUser) -> Result<(), (StatusCode, Json<Value>)> {
|
||||
if !user.role.is_admin() {
|
||||
fn require_write_access(user: &AuthUser) -> Result<(), (StatusCode, Json<Value>)> {
|
||||
if !user.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
@ -240,7 +240,7 @@ async fn download_client_cert(
|
||||
auth: AuthUser,
|
||||
Path(host_id): Path<Uuid>,
|
||||
) -> Result<Response<Body>, (StatusCode, Json<Value>)> {
|
||||
require_admin(&auth)?;
|
||||
require_write_access(&auth)?;
|
||||
|
||||
let cert_pem: Option<String> = sqlx::query_scalar(
|
||||
r#"SELECT cert_pem
|
||||
@ -297,7 +297,7 @@ async fn issue_client_cert(
|
||||
Path(host_id): Path<Uuid>,
|
||||
Json(req): Json<IssueCertRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
require_admin(&auth)?;
|
||||
require_write_access(&auth)?;
|
||||
|
||||
// Look up the host's IP address from the database.
|
||||
let ip_address: String = sqlx::query_scalar("SELECT host(ip_address) FROM hosts WHERE id = $1")
|
||||
@ -353,7 +353,7 @@ async fn renew_cert(
|
||||
auth: AuthUser,
|
||||
Path(cert_id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
require_admin(&auth)?;
|
||||
require_write_access(&auth)?;
|
||||
|
||||
let issued = state.ca.renew_cert(cert_id, &state.db).await.map_err(|e| {
|
||||
let msg = e.to_string();
|
||||
@ -398,7 +398,7 @@ async fn reissue_host_cert(
|
||||
auth: AuthUser,
|
||||
Path(host_id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
require_admin(&auth)?;
|
||||
require_write_access(&auth)?;
|
||||
|
||||
// Look up the host's FQDN and IP address for the new certificate CN and SANs.
|
||||
let row = sqlx::query("SELECT fqdn, host(ip_address) AS ip_address FROM hosts WHERE id = $1")
|
||||
@ -475,7 +475,7 @@ async fn revoke_cert(
|
||||
auth: AuthUser,
|
||||
Path(cert_id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
require_admin(&auth)?;
|
||||
require_write_access(&auth)?;
|
||||
|
||||
state
|
||||
.ca
|
||||
|
||||
@ -45,10 +45,10 @@ async fn start_cidr_scan(
|
||||
auth: AuthUser,
|
||||
Json(req): Json<DiscoveryCidrRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -221,10 +221,10 @@ async fn register_discovered_host(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<RegisterDiscoveredRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -62,10 +62,10 @@ async fn create_group(
|
||||
auth: AuthUser,
|
||||
Json(req): Json<CreateGroupRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -154,10 +154,10 @@ async fn update_group(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<UpdateGroupRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -187,10 +187,10 @@ async fn delete_group(
|
||||
auth: AuthUser,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -234,10 +234,10 @@ async fn add_user_to_group(
|
||||
auth: AuthUser,
|
||||
Path((id, user_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -276,10 +276,10 @@ async fn remove_user_from_group(
|
||||
auth: AuthUser,
|
||||
Path((id, user_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -102,8 +102,8 @@ async fn list_health_checks(
|
||||
auth: AuthUser,
|
||||
Path(host_id): Path<Uuid>,
|
||||
) -> Result<Json<HealthCheckListResponse>, (StatusCode, Json<Value>)> {
|
||||
// RBAC check for operators
|
||||
if !auth.role.is_admin() {
|
||||
// RBAC: reporters can only see hosts in their groups
|
||||
if !auth.role.can_write() {
|
||||
let can_access = operator_can_access_host(&state.db, auth.user_id, host_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@ -208,25 +208,11 @@ async fn create_health_check(
|
||||
Path(host_id): Path<Uuid>,
|
||||
Json(req): Json<CreateHealthCheckRequest>,
|
||||
) -> Result<(StatusCode, Json<Value>), (StatusCode, Json<Value>)> {
|
||||
// RBAC check for operators
|
||||
if !auth.role.is_admin() {
|
||||
let can_access = operator_can_access_host(&state.db, auth.user_id, host_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "RBAC check failed");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?;
|
||||
if !can_access {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(
|
||||
json!({ "error": { "code": "forbidden", "message": "Not authorized for this host" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
// Validate check_type
|
||||
@ -426,8 +412,8 @@ async fn get_health_check(
|
||||
auth: AuthUser,
|
||||
Path((host_id, check_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<HealthCheckWithResult>, (StatusCode, Json<Value>)> {
|
||||
// RBAC check for operators
|
||||
if !auth.role.is_admin() {
|
||||
// RBAC: reporters can only see hosts in their groups
|
||||
if !auth.role.can_write() {
|
||||
let can_access = operator_can_access_host(&state.db, auth.user_id, host_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@ -506,25 +492,11 @@ async fn update_health_check(
|
||||
Path((host_id, check_id)): Path<(Uuid, Uuid)>,
|
||||
Json(req): Json<UpdateHealthCheckRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
// RBAC check for operators
|
||||
if !auth.role.is_admin() {
|
||||
let can_access = operator_can_access_host(&state.db, auth.user_id, host_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "RBAC check failed");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?;
|
||||
if !can_access {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(
|
||||
json!({ "error": { "code": "forbidden", "message": "Not authorized for this host" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
// Verify check exists and belongs to host
|
||||
@ -746,25 +718,11 @@ async fn delete_health_check(
|
||||
auth: AuthUser,
|
||||
Path((host_id, check_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<StatusCode, (StatusCode, Json<Value>)> {
|
||||
// RBAC check for operators
|
||||
if !auth.role.is_admin() {
|
||||
let can_access = operator_can_access_host(&state.db, auth.user_id, host_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "RBAC check failed");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?;
|
||||
if !can_access {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(
|
||||
json!({ "error": { "code": "forbidden", "message": "Not authorized for this host" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
let deleted = sqlx::query("DELETE FROM host_health_checks WHERE id = $1 AND host_id = $2")
|
||||
@ -811,25 +769,11 @@ async fn test_health_check(
|
||||
auth: AuthUser,
|
||||
Path((host_id, check_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<HealthCheckTestResponse>, (StatusCode, Json<Value>)> {
|
||||
// RBAC check for operators
|
||||
if !auth.role.is_admin() {
|
||||
let can_access = operator_can_access_host(&state.db, auth.user_id, host_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "RBAC check failed");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
||||
)
|
||||
})?;
|
||||
if !can_access {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(
|
||||
json!({ "error": { "code": "forbidden", "message": "Not authorized for this host" } }),
|
||||
),
|
||||
));
|
||||
}
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
// Get the health check
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//! GET /api/v1/hosts/{id}/groups — list groups for host
|
||||
//! POST /api/v1/hosts/{id}/groups — assign host to group
|
||||
//! DELETE /api/v1/hosts/{id}/groups/{group_id} — remove host from group
|
||||
//! POST /api/v1/hosts/{id}/refresh — queue on-demand refresh (operator+)
|
||||
//! POST /api/v1/hosts/{id}/refresh — queue on-demand refresh (write access)
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
@ -214,10 +214,10 @@ async fn register_host(
|
||||
Json(req): Json<CreateHostRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
// Admin only
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -348,10 +348,10 @@ async fn remove_host(
|
||||
auth: AuthUser,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -451,10 +451,10 @@ async fn add_host_to_group(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<AddToGroupRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -496,10 +496,10 @@ async fn remove_host_from_group(
|
||||
auth: AuthUser,
|
||||
Path((id, group_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
|
||||
@ -562,9 +562,15 @@ async fn resolve_fqdn(fqdn: &str) -> Result<String, String> {
|
||||
/// Requires Operator or Admin role (any authenticated user).
|
||||
async fn refresh_host(
|
||||
State(state): State<AppState>,
|
||||
_auth: AuthUser,
|
||||
auth: AuthUser,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<(StatusCode, Json<Value>), (StatusCode, Json<Value>)> {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
// Verify the host exists.
|
||||
let exists: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM hosts WHERE id = $1)")
|
||||
.bind(id)
|
||||
|
||||
@ -116,6 +116,13 @@ async fn create_job(
|
||||
auth: AuthUser,
|
||||
Json(req): Json<CreateJobRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.can_write() {
|
||||
return Err(err(
|
||||
StatusCode::FORBIDDEN,
|
||||
"forbidden",
|
||||
"Write access required",
|
||||
));
|
||||
}
|
||||
if req.host_ids.is_empty() {
|
||||
return Err(err(
|
||||
StatusCode::BAD_REQUEST,
|
||||
@ -430,13 +437,13 @@ async fn cancel_job(
|
||||
row.ok_or_else(|| err(StatusCode::NOT_FOUND, "not_found", "Job not found"))?;
|
||||
|
||||
// Only admin or the job creator may cancel.
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
let is_creator = creator_id.map_or(false, |cid| cid == auth.user_id);
|
||||
if !is_creator {
|
||||
return Err(err(
|
||||
StatusCode::FORBIDDEN,
|
||||
"forbidden",
|
||||
"Only admin or the job creator may cancel this job",
|
||||
"Write access required",
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -535,11 +542,11 @@ async fn rollback_job(
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
// Admin-only operation.
|
||||
if !auth.role.is_admin() {
|
||||
if !auth.role.can_write() {
|
||||
return Err(err(
|
||||
StatusCode::FORBIDDEN,
|
||||
"forbidden",
|
||||
"Admin role required to create rollback jobs",
|
||||
"Write access required",
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -103,6 +103,13 @@ async fn create_window(
|
||||
Path(host_id): Path<Uuid>,
|
||||
Json(req): Json<CreateMaintenanceWindowRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.can_write() {
|
||||
return Err(err(
|
||||
StatusCode::FORBIDDEN,
|
||||
"forbidden",
|
||||
"Write access required",
|
||||
));
|
||||
}
|
||||
// Validate: weekly requires recurrence_day 0-6
|
||||
if req.recurrence == pm_core::models::WindowRecurrence::Weekly {
|
||||
match req.recurrence_day {
|
||||
@ -218,6 +225,13 @@ async fn update_window(
|
||||
Path((host_id, win_id)): Path<(Uuid, Uuid)>,
|
||||
Json(req): Json<UpdateMaintenanceWindowRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.can_write() {
|
||||
return Err(err(
|
||||
StatusCode::FORBIDDEN,
|
||||
"forbidden",
|
||||
"Write access required",
|
||||
));
|
||||
}
|
||||
// Fetch existing record (verify ownership and existence).
|
||||
let existing: Option<MaintenanceWindow> = sqlx::query_as(
|
||||
r#"
|
||||
@ -349,6 +363,13 @@ async fn delete_window(
|
||||
auth: AuthUser,
|
||||
Path((host_id, win_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
if !auth.role.can_write() {
|
||||
return Err(err(
|
||||
StatusCode::FORBIDDEN,
|
||||
"forbidden",
|
||||
"Write access required",
|
||||
));
|
||||
}
|
||||
let result = sqlx::query("DELETE FROM maintenance_windows WHERE id = $1 AND host_id = $2")
|
||||
.bind(win_id)
|
||||
.bind(host_id)
|
||||
|
||||
@ -169,11 +169,11 @@ pub fn router() -> Router<AppState> {
|
||||
|
||||
const MASKED: &str = "********";
|
||||
|
||||
fn admin_only(auth: &AuthUser) -> Result<(), (StatusCode, Json<Value>)> {
|
||||
if !auth.role.is_admin() {
|
||||
fn write_access_required(auth: &AuthUser) -> Result<(), (StatusCode, Json<Value>)> {
|
||||
if !auth.role.can_write() {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Admin access required" } })),
|
||||
Json(json!({ "error": { "code": "forbidden", "message": "Write access required" } })),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
@ -311,7 +311,7 @@ async fn get_settings(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
) -> Result<Json<SettingsResponse>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
let cfg = load_system_config(&state.db).await?;
|
||||
// Inject read-only config values from TOML file (not stored in DB)
|
||||
let mut cfg = cfg;
|
||||
@ -332,7 +332,7 @@ async fn update_settings(
|
||||
auth: AuthUser,
|
||||
Json(req): Json<UpdateSettingsRequest>,
|
||||
) -> Result<Json<SettingsResponse>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
// Update OIDC config
|
||||
if let Some(oidc) = req.oidc {
|
||||
@ -562,7 +562,7 @@ async fn discover_oidc(
|
||||
auth: AuthUser,
|
||||
Json(req): Json<OidcDiscoveryRequest>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
if req.discovery_url.is_empty() {
|
||||
return Err((
|
||||
@ -619,7 +619,7 @@ async fn test_oidc(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
let row: Option<(bool, String, String)> = sqlx::query_as(
|
||||
"SELECT enabled, provider_type, discovery_url FROM oidc_config WHERE id = 1",
|
||||
@ -715,7 +715,7 @@ async fn test_smtp(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
let cfg = load_system_config(&state.db).await?;
|
||||
|
||||
@ -870,7 +870,7 @@ async fn get_ip_whitelist(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
let value: Option<String> = sqlx::query_scalar(
|
||||
"SELECT value FROM system_config WHERE key = 'ip_whitelist'",
|
||||
@ -898,7 +898,7 @@ async fn update_ip_whitelist(
|
||||
auth: AuthUser,
|
||||
Json(req): Json<IpWhitelistUpdate>,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
// Validate each entry
|
||||
for entry in &req.entries {
|
||||
@ -943,7 +943,7 @@ async fn audit_integrity(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
|
||||
admin_only(&auth)?;
|
||||
write_access_required(&auth)?;
|
||||
|
||||
let result = verify_integrity(&state.db).await;
|
||||
|
||||
|
||||
@ -504,9 +504,9 @@ async fn sso_callback(
|
||||
None => {
|
||||
// No existing user - create new one
|
||||
let id: Uuid = match sqlx::query_scalar(
|
||||
r#"INSERT INTO users (username, display_name, email, role, auth_provider, azure_oid, oidc_sub)
|
||||
VALUES ($1, $2, $3, 'operator'::user_role, $4::auth_provider, $5, $6)
|
||||
RETURNING id"#,
|
||||
r#"INSERT INTO users (username, display_name, email, role, auth_provider, azure_oid, oidc_sub)
|
||||
VALUES ($1, $2, $3, 'reporter'::user_role, $4::auth_provider, $5, $6)
|
||||
RETURNING id"#,
|
||||
)
|
||||
.bind(&preferred_username)
|
||||
.bind(&name)
|
||||
@ -541,7 +541,7 @@ async fn sso_callback(
|
||||
id,
|
||||
username: preferred_username,
|
||||
display_name: name,
|
||||
role: "operator".to_string(),
|
||||
role: "reporter".to_string(),
|
||||
is_active: true,
|
||||
mfa_enabled: false,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user