feat: complete NX9-Auth management and integrity hardening
This commit is contained in:
1 parent
dc5417334b
commit
a969f9c571
59 files changed
+6508
-290
No files matched your search
+71
-10
@@ -84,6 +84,7 @@ pub async fn list_audit(
|
||||
since: query.since,
|
||||
until: query.until,
|
||||
search: query.q,
|
||||
success: query.success,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
@@ -92,19 +93,10 @@ pub async fn list_audit(
|
||||
.await
|
||||
.map_err(AppError::Database)?;
|
||||
|
||||
let mut entries = audit_repo::list_filtered(&state.provider, &filter)
|
||||
let entries = audit_repo::list_filtered(&state.provider, &filter)
|
||||
.await
|
||||
.map_err(AppError::Database)?;
|
||||
|
||||
if let Some(success) = query.success {
|
||||
entries.retain(|e| {
|
||||
let ok = !e.action.contains("fail")
|
||||
&& !e.action.contains("denied")
|
||||
&& e.severity != "critical";
|
||||
ok == success
|
||||
});
|
||||
}
|
||||
|
||||
let views: Vec<AuditLogResponse> = entries.into_iter().map(AuditLogResponse::from).collect();
|
||||
|
||||
Ok(Json(json!({
|
||||
@@ -114,3 +106,72 @@ pub async fn list_audit(
|
||||
"offset": offset,
|
||||
})))
|
||||
}
|
||||
|
||||
/// GET /api/v1/audit/export
|
||||
pub async fn export_audit(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
Query(query): Query<AuditQuery>,
|
||||
) -> Result<axum::response::Response> {
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
require(&state.provider, &auth.user.id, "audit:view").await?;
|
||||
|
||||
let limit = query.limit.unwrap_or(5000).clamp(1, 5000);
|
||||
let offset = query.offset.unwrap_or(0).max(0);
|
||||
|
||||
let filter = AuditFilter {
|
||||
actor_user_id: query.actor,
|
||||
action: query.action,
|
||||
resource_type: query.resource_type,
|
||||
severity: query.severity,
|
||||
since: query.since,
|
||||
until: query.until,
|
||||
search: query.q,
|
||||
success: query.success,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
|
||||
let entries = audit_repo::list_filtered(&state.provider, &filter)
|
||||
.await
|
||||
.map_err(AppError::Database)?;
|
||||
|
||||
let mut csv = String::from(
|
||||
"id,created_at,action,resource_type,resource_id,severity,success,actor_user_id,target_user_id,ip_address,user_agent,metadata_json\r\n",
|
||||
);
|
||||
for e in entries {
|
||||
let resp = AuditLogResponse::from(e);
|
||||
let esc = |s: &str| format!("\"{}\"", s.replace('"', "\"\""));
|
||||
let line = format!(
|
||||
"{},{},{},{},{},{},{},{},{},{},{},{}\r\n",
|
||||
esc(&resp.id),
|
||||
esc(&resp.created_at),
|
||||
esc(&resp.action),
|
||||
esc(&resp.resource_type),
|
||||
esc(resp.resource_id.as_deref().unwrap_or("")),
|
||||
esc(&resp.severity),
|
||||
resp.success,
|
||||
esc(resp.actor_user_id.as_deref().unwrap_or("")),
|
||||
esc(resp.target_user_id.as_deref().unwrap_or("")),
|
||||
esc(resp.ip_address.as_deref().unwrap_or("")),
|
||||
esc(resp.user_agent.as_deref().unwrap_or("")),
|
||||
esc(resp.metadata_json.as_deref().unwrap_or("")),
|
||||
);
|
||||
csv.push_str(&line);
|
||||
}
|
||||
|
||||
let response = (
|
||||
[
|
||||
(axum::http::header::CONTENT_TYPE, "text/csv; charset=utf-8"),
|
||||
(
|
||||
axum::http::header::CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"audit_export.csv\"",
|
||||
),
|
||||
],
|
||||
csv,
|
||||
)
|
||||
.into_response();
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
Reference in new issue
Block a user