koprogo_api/infrastructure/web/handlers/
gdpr_handlers.rs1use crate::application::dto::{
2 GdprActionResponse, GdprMarketingPreferenceRequest, GdprRectifyRequest,
3 GdprRestrictProcessingRequest,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, put, web, HttpRequest, HttpResponse, Responder};
8use chrono::Utc;
9use tokio::spawn;
10
11fn extract_ip_address(req: &HttpRequest) -> Option<String> {
13 req.headers()
15 .get("X-Forwarded-For")
16 .and_then(|h| h.to_str().ok())
17 .map(|s| s.split(',').next().unwrap_or("").trim().to_string())
18 .filter(|s| !s.is_empty())
19 .or_else(|| {
20 req.headers()
22 .get("X-Real-IP")
23 .and_then(|h| h.to_str().ok())
24 .map(|s| s.to_string())
25 })
26 .or_else(|| {
27 req.peer_addr().map(|addr| addr.ip().to_string())
29 })
30}
31
32fn extract_user_agent(req: &HttpRequest) -> Option<String> {
34 req.headers()
35 .get("User-Agent")
36 .and_then(|h| h.to_str().ok())
37 .map(|s| s.to_string())
38}
39
40#[get("/gdpr/export")]
50pub async fn export_user_data(
51 req: HttpRequest,
52 data: web::Data<AppState>,
53 auth: AuthenticatedUser,
54) -> impl Responder {
55 let user_id = auth.user_id;
57
58 let ip_address = extract_ip_address(&req);
60 let user_agent = extract_user_agent(&req);
61
62 let organization_id = if auth.role == "superadmin" {
66 None
67 } else {
68 auth.organization_id
69 };
70
71 match data
73 .gdpr_use_cases
74 .export_user_data(user_id, user_id, organization_id)
75 .await
76 {
77 Ok(export_data) => {
78 let user_email = export_data.user.email.clone();
80 let user_name = format!(
81 "{} {}",
82 export_data.user.first_name, export_data.user.last_name
83 );
84
85 let audit_entry = AuditLogEntry::new(
87 AuditEventType::GdprDataExported,
88 Some(user_id),
89 organization_id,
90 )
91 .with_resource("User", user_id)
92 .with_client_info(ip_address, user_agent)
93 .with_metadata(serde_json::json!({
94 "total_items": export_data.total_items,
95 "export_date": export_data.export_date
96 }));
97
98 let audit_logger = data.audit_logger.clone();
99 spawn(async move {
100 audit_logger.log(&audit_entry).await;
101 });
102
103 let email_service = data.email_service.clone();
105 spawn(async move {
106 if let Err(e) = email_service
107 .send_gdpr_export_notification(&user_email, &user_name, user_id)
108 .await
109 {
110 log::error!("Failed to send GDPR export email notification: {}", e);
111 }
112 });
113
114 HttpResponse::Ok().json(export_data)
115 }
116 Err(e) => {
117 let audit_entry = AuditLogEntry::new(
119 AuditEventType::GdprDataExportFailed,
120 Some(user_id),
121 organization_id,
122 )
123 .with_resource("User", user_id)
124 .with_client_info(ip_address, user_agent)
125 .with_error(e.clone());
126
127 let audit_logger = data.audit_logger.clone();
128 spawn(async move {
129 audit_logger.log(&audit_entry).await;
130 });
131
132 if e.contains("not found") {
133 HttpResponse::NotFound().json(serde_json::json!({
134 "error": e
135 }))
136 } else if e.contains("Unauthorized") {
137 HttpResponse::Forbidden().json(serde_json::json!({
138 "error": e
139 }))
140 } else if e.contains("anonymized") {
141 HttpResponse::Gone().json(serde_json::json!({
142 "error": e
143 }))
144 } else {
145 HttpResponse::InternalServerError().json(serde_json::json!({
146 "error": format!("Failed to export user data: {}", e)
147 }))
148 }
149 }
150 }
151}
152
153#[delete("/gdpr/erase")]
168pub async fn erase_user_data(
169 req: HttpRequest,
170 data: web::Data<AppState>,
171 auth: AuthenticatedUser,
172) -> impl Responder {
173 let user_id = auth.user_id;
175
176 let ip_address = extract_ip_address(&req);
178 let user_agent = extract_user_agent(&req);
179
180 let organization_id = if auth.role == "superadmin" {
182 None
183 } else {
184 auth.organization_id
185 };
186
187 match data
189 .gdpr_use_cases
190 .erase_user_data(user_id, user_id, organization_id)
191 .await
192 {
193 Ok(erase_response) => {
194 let user_email = erase_response.user_email.clone();
196 let user_name = format!(
197 "{} {}",
198 erase_response.user_first_name, erase_response.user_last_name
199 );
200 let owners_count = erase_response.owners_anonymized;
201
202 let audit_entry = AuditLogEntry::new(
204 AuditEventType::GdprDataErased,
205 Some(user_id),
206 organization_id,
207 )
208 .with_resource("User", user_id)
209 .with_client_info(ip_address, user_agent)
210 .with_metadata(serde_json::json!({
211 "owners_anonymized": erase_response.owners_anonymized,
212 "anonymized_at": erase_response.anonymized_at
213 }));
214
215 let audit_logger = data.audit_logger.clone();
216 spawn(async move {
217 audit_logger.log(&audit_entry).await;
218 });
219
220 let email_service = data.email_service.clone();
222 spawn(async move {
223 if let Err(e) = email_service
224 .send_gdpr_erasure_notification(&user_email, &user_name, owners_count)
225 .await
226 {
227 log::error!("Failed to send GDPR erasure email notification: {}", e);
228 }
229 });
230
231 HttpResponse::Ok().json(erase_response)
232 }
233 Err(e) => {
234 let audit_entry = AuditLogEntry::new(
236 AuditEventType::GdprDataErasureFailed,
237 Some(user_id),
238 organization_id,
239 )
240 .with_resource("User", user_id)
241 .with_client_info(ip_address, user_agent)
242 .with_error(e.clone());
243
244 let audit_logger = data.audit_logger.clone();
245 spawn(async move {
246 audit_logger.log(&audit_entry).await;
247 });
248
249 if e.contains("Unauthorized") {
250 HttpResponse::Forbidden().json(serde_json::json!({
251 "error": e
252 }))
253 } else if e.contains("already anonymized") {
254 HttpResponse::Gone().json(serde_json::json!({
255 "error": e
256 }))
257 } else if e.contains("legal holds") {
258 HttpResponse::Conflict().json(serde_json::json!({
259 "error": e,
260 "message": "Cannot erase data due to legal obligations. Please resolve pending issues before requesting erasure."
261 }))
262 } else if e.contains("not found") {
263 HttpResponse::NotFound().json(serde_json::json!({
264 "error": e
265 }))
266 } else {
267 HttpResponse::InternalServerError().json(serde_json::json!({
268 "error": format!("Failed to erase user data: {}", e)
269 }))
270 }
271 }
272 }
273}
274
275#[get("/gdpr/can-erase")]
283pub async fn can_erase_user(
284 req: HttpRequest,
285 data: web::Data<AppState>,
286 auth: AuthenticatedUser,
287) -> impl Responder {
288 let user_id = auth.user_id;
289
290 let ip_address = extract_ip_address(&req);
292 let user_agent = extract_user_agent(&req);
293
294 match data.gdpr_use_cases.can_erase_user(user_id).await {
295 Ok(can_erase) => {
296 let audit_entry = AuditLogEntry::new(
298 AuditEventType::GdprErasureCheckRequested,
299 Some(user_id),
300 auth.organization_id,
301 )
302 .with_resource("User", user_id)
303 .with_client_info(ip_address, user_agent)
304 .with_metadata(serde_json::json!({
305 "can_erase": can_erase
306 }));
307
308 let audit_logger = data.audit_logger.clone();
309 spawn(async move {
310 audit_logger.log(&audit_entry).await;
311 });
312
313 HttpResponse::Ok().json(serde_json::json!({
314 "can_erase": can_erase,
315 "user_id": user_id.to_string()
316 }))
317 }
318 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
319 "error": format!("Failed to check erasure eligibility: {}", e)
320 })),
321 }
322}
323
324#[put("/gdpr/rectify")]
346pub async fn rectify_user_data(
347 req: HttpRequest,
348 data: web::Data<AppState>,
349 auth: AuthenticatedUser,
350 request: web::Json<GdprRectifyRequest>,
351) -> impl Responder {
352 let user_id = auth.user_id;
353
354 let ip_address = extract_ip_address(&req);
356 let user_agent = extract_user_agent(&req);
357
358 match data
360 .gdpr_use_cases
361 .rectify_user_data(
362 user_id,
363 user_id, request.email.clone(),
365 request.first_name.clone(),
366 request.last_name.clone(),
367 )
368 .await
369 {
370 Ok(_) => {
371 let audit_entry = AuditLogEntry::new(
373 AuditEventType::GdprDataRectified,
374 Some(user_id),
375 auth.organization_id,
376 )
377 .with_resource("User", user_id)
378 .with_client_info(ip_address, user_agent)
379 .with_metadata(serde_json::json!({
380 "fields_updated": {
381 "email": request.email.is_some(),
382 "first_name": request.first_name.is_some(),
383 "last_name": request.last_name.is_some()
384 }
385 }));
386
387 let audit_logger = data.audit_logger.clone();
388 spawn(async move {
389 audit_logger.log(&audit_entry).await;
390 });
391
392 let response = GdprActionResponse {
393 success: true,
394 message: "Personal data successfully rectified".to_string(),
395 updated_at: Utc::now().to_rfc3339(),
396 };
397
398 HttpResponse::Ok().json(response)
399 }
400 Err(e) => {
401 let audit_entry = AuditLogEntry::new(
403 AuditEventType::GdprDataRectificationFailed,
404 Some(user_id),
405 auth.organization_id,
406 )
407 .with_resource("User", user_id)
408 .with_client_info(ip_address, user_agent)
409 .with_error(e.clone());
410
411 let audit_logger = data.audit_logger.clone();
412 spawn(async move {
413 audit_logger.log(&audit_entry).await;
414 });
415
416 if e.contains("Unauthorized") {
417 HttpResponse::Forbidden().json(serde_json::json!({
418 "error": e
419 }))
420 } else if e.contains("not found") {
421 HttpResponse::NotFound().json(serde_json::json!({
422 "error": e
423 }))
424 } else if e.contains("Validation error") {
425 HttpResponse::BadRequest().json(serde_json::json!({
426 "error": e
427 }))
428 } else {
429 HttpResponse::InternalServerError().json(serde_json::json!({
430 "error": format!("Failed to rectify user data: {}", e)
431 }))
432 }
433 }
434 }
435}
436
437#[put("/gdpr/restrict-processing")]
454pub async fn restrict_user_processing(
455 req: HttpRequest,
456 data: web::Data<AppState>,
457 auth: AuthenticatedUser,
458 _request: web::Json<GdprRestrictProcessingRequest>,
459) -> impl Responder {
460 let user_id = auth.user_id;
461
462 let ip_address = extract_ip_address(&req);
464 let user_agent = extract_user_agent(&req);
465
466 match data
468 .gdpr_use_cases
469 .restrict_user_processing(user_id, user_id)
470 .await
471 {
472 Ok(_) => {
473 let audit_entry = AuditLogEntry::new(
475 AuditEventType::GdprProcessingRestricted,
476 Some(user_id),
477 auth.organization_id,
478 )
479 .with_resource("User", user_id)
480 .with_client_info(ip_address, user_agent);
481
482 let audit_logger = data.audit_logger.clone();
483 spawn(async move {
484 audit_logger.log(&audit_entry).await;
485 });
486
487 let response = GdprActionResponse {
488 success: true,
489 message: "Data processing successfully restricted. Your data will be stored but not processed for certain operations.".to_string(),
490 updated_at: Utc::now().to_rfc3339(),
491 };
492
493 HttpResponse::Ok().json(response)
494 }
495 Err(e) => {
496 let audit_entry = AuditLogEntry::new(
498 AuditEventType::GdprProcessingRestrictionFailed,
499 Some(user_id),
500 auth.organization_id,
501 )
502 .with_resource("User", user_id)
503 .with_client_info(ip_address, user_agent)
504 .with_error(e.clone());
505
506 let audit_logger = data.audit_logger.clone();
507 spawn(async move {
508 audit_logger.log(&audit_entry).await;
509 });
510
511 if e.contains("Unauthorized") {
512 HttpResponse::Forbidden().json(serde_json::json!({
513 "error": e
514 }))
515 } else if e.contains("not found") {
516 HttpResponse::NotFound().json(serde_json::json!({
517 "error": e
518 }))
519 } else if e.contains("already restricted") {
520 HttpResponse::BadRequest().json(serde_json::json!({
521 "error": e
522 }))
523 } else {
524 HttpResponse::InternalServerError().json(serde_json::json!({
525 "error": format!("Failed to restrict processing: {}", e)
526 }))
527 }
528 }
529 }
530}
531
532#[put("/gdpr/marketing-preference")]
551pub async fn set_marketing_preference(
552 req: HttpRequest,
553 data: web::Data<AppState>,
554 auth: AuthenticatedUser,
555 request: web::Json<GdprMarketingPreferenceRequest>,
556) -> impl Responder {
557 let user_id = auth.user_id;
558
559 let ip_address = extract_ip_address(&req);
561 let user_agent = extract_user_agent(&req);
562
563 let opt_out = request.opt_out;
564
565 match data
567 .gdpr_use_cases
568 .set_marketing_preference(user_id, user_id, opt_out)
569 .await
570 {
571 Ok(_) => {
572 let event_type = if opt_out {
574 AuditEventType::GdprMarketingOptOut
575 } else {
576 AuditEventType::GdprMarketingOptIn
577 };
578
579 let audit_entry = AuditLogEntry::new(event_type, Some(user_id), auth.organization_id)
580 .with_resource("User", user_id)
581 .with_client_info(ip_address, user_agent)
582 .with_metadata(serde_json::json!({
583 "opt_out": opt_out
584 }));
585
586 let audit_logger = data.audit_logger.clone();
587 spawn(async move {
588 audit_logger.log(&audit_entry).await;
589 });
590
591 let message = if opt_out {
592 "You have successfully opted out of marketing communications. You will no longer receive promotional emails or offers."
593 } else {
594 "You have successfully opted back in to marketing communications. You will receive promotional emails and offers."
595 };
596
597 let response = GdprActionResponse {
598 success: true,
599 message: message.to_string(),
600 updated_at: Utc::now().to_rfc3339(),
601 };
602
603 HttpResponse::Ok().json(response)
604 }
605 Err(e) => {
606 let audit_entry = AuditLogEntry::new(
608 AuditEventType::GdprMarketingPreferenceChangeFailed,
609 Some(user_id),
610 auth.organization_id,
611 )
612 .with_resource("User", user_id)
613 .with_client_info(ip_address, user_agent)
614 .with_error(e.clone());
615
616 let audit_logger = data.audit_logger.clone();
617 spawn(async move {
618 audit_logger.log(&audit_entry).await;
619 });
620
621 if e.contains("Unauthorized") {
622 HttpResponse::Forbidden().json(serde_json::json!({
623 "error": e
624 }))
625 } else if e.contains("not found") {
626 HttpResponse::NotFound().json(serde_json::json!({
627 "error": e
628 }))
629 } else {
630 HttpResponse::InternalServerError().json(serde_json::json!({
631 "error": format!("Failed to set marketing preference: {}", e)
632 }))
633 }
634 }
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 #[test]
644 fn test_handler_structure_export() {
645 }
648
649 #[test]
650 fn test_handler_structure_erase() {
651 }
653
654 #[test]
655 fn test_handler_structure_can_erase() {
656 }
658}