mockforge_registry_server/handlers/
security.rs1use axum::{
6 extract::{Path, Query, State},
7 http::HeaderMap,
8 Json,
9};
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13use crate::{
14 error::{ApiError, ApiResult},
15 middleware::{resolve_org_context, AuthUser},
16 AppState,
17};
18
19#[derive(Debug, Serialize)]
20pub struct SuspiciousActivityResponse {
21 pub id: Uuid,
22 pub org_id: Option<Uuid>,
23 pub user_id: Option<Uuid>,
24 pub activity_type: String,
25 pub severity: String,
26 pub description: String,
27 pub metadata: Option<serde_json::Value>,
28 pub ip_address: Option<String>,
29 pub user_agent: Option<String>,
30 pub resolved: bool,
31 pub resolved_at: Option<chrono::DateTime<chrono::Utc>>,
32 pub created_at: chrono::DateTime<chrono::Utc>,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct SuspiciousActivityQuery {
37 pub severity: Option<String>,
38 pub limit: Option<i64>,
39}
40
41#[derive(Debug, Serialize)]
42pub struct SuspiciousActivityListResponse {
43 pub activities: Vec<SuspiciousActivityResponse>,
44 pub total: i64,
45}
46
47pub async fn get_suspicious_activities(
49 State(state): State<AppState>,
50 AuthUser(user_id): AuthUser,
51 headers: HeaderMap,
52 Query(query): Query<SuspiciousActivityQuery>,
53) -> ApiResult<Json<SuspiciousActivityListResponse>> {
54 let org_ctx = resolve_org_context(&state, user_id, &headers, None)
56 .await
57 .map_err(|_| ApiError::InvalidRequest("Organization not found".to_string()))?;
58
59 let activities = state
61 .store
62 .list_unresolved_suspicious_activities(
63 Some(org_ctx.org_id),
64 None,
65 query.severity.as_deref(),
66 query.limit.or(Some(100)),
67 )
68 .await?;
69
70 let total = state.store.count_unresolved_suspicious_activities(org_ctx.org_id).await?;
72
73 let activity_responses: Vec<SuspiciousActivityResponse> = activities
74 .into_iter()
75 .map(|a| SuspiciousActivityResponse {
76 id: a.id,
77 org_id: a.org_id,
78 user_id: a.user_id,
79 activity_type: format!("{:?}", a.activity_type),
80 severity: a.severity,
81 description: a.description,
82 metadata: a.metadata,
83 ip_address: a.ip_address,
84 user_agent: a.user_agent,
85 resolved: a.resolved,
86 resolved_at: a.resolved_at,
87 created_at: a.created_at,
88 })
89 .collect();
90
91 Ok(Json(SuspiciousActivityListResponse {
92 activities: activity_responses,
93 total,
94 }))
95}
96
97pub async fn resolve_suspicious_activity(
103 State(state): State<AppState>,
104 AuthUser(user_id): AuthUser,
105 headers: HeaderMap,
106 Path(activity_id): Path<Uuid>,
107) -> ApiResult<Json<serde_json::Value>> {
108 let org_ctx = resolve_org_context(&state, user_id, &headers, None)
109 .await
110 .map_err(|_| ApiError::InvalidRequest("Organization not found".to_string()))?;
111
112 state
113 .store
114 .resolve_suspicious_activity(org_ctx.org_id, activity_id, user_id)
115 .await?;
116
117 Ok(Json(serde_json::json!({
118 "success": true,
119 "message": "Suspicious activity marked as resolved"
120 })))
121}