1use 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#[utoipa::path(
50 get,
51 path = "/gdpr/export",
52 tag = "GDPR",
53 summary = "Export user personal data (Article 15 - Right to Access)",
54 responses(
55 (status = 200, description = "User data exported"),
56 (status = 401, description = "Unauthorized"),
57 (status = 403, description = "Forbidden"),
58 (status = 404, description = "User not found"),
59 (status = 410, description = "User already anonymized"),
60 (status = 500, description = "Internal server error"),
61 ),
62 security(("bearer_auth" = []))
63)]
64#[get("/gdpr/export")]
65pub async fn export_user_data(
66 req: HttpRequest,
67 data: web::Data<AppState>,
68 auth: AuthenticatedUser,
69) -> impl Responder {
70 let user_id = auth.user_id;
72
73 let ip_address = extract_ip_address(&req);
75 let user_agent = extract_user_agent(&req);
76
77 let organization_id = if auth.role == "superadmin" {
81 None
82 } else {
83 auth.organization_id
84 };
85
86 match data
88 .gdpr_use_cases
89 .export_user_data(user_id, user_id, organization_id)
90 .await
91 {
92 Ok(export_data) => {
93 let user_email = export_data.user.email.clone();
95 let user_name = format!(
96 "{} {}",
97 export_data.user.first_name, export_data.user.last_name
98 );
99
100 let audit_entry = AuditLogEntry::new(
102 AuditEventType::GdprDataExported,
103 Some(user_id),
104 organization_id,
105 )
106 .with_resource("User", user_id)
107 .with_client_info(ip_address, user_agent)
108 .with_metadata(serde_json::json!({
109 "total_items": export_data.total_items,
110 "export_date": export_data.export_date
111 }));
112
113 let audit_logger = data.audit_logger.clone();
114 spawn(async move {
115 audit_logger.log(&audit_entry).await;
116 });
117
118 let email_service = data.email_service.clone();
120 spawn(async move {
121 if let Err(e) = email_service
122 .send_gdpr_export_notification(&user_email, &user_name, user_id)
123 .await
124 {
125 log::error!("Failed to send GDPR export email notification: {}", e);
126 }
127 });
128
129 HttpResponse::Ok().json(export_data)
130 }
131 Err(e) => {
132 let audit_entry = AuditLogEntry::new(
134 AuditEventType::GdprDataExportFailed,
135 Some(user_id),
136 organization_id,
137 )
138 .with_resource("User", user_id)
139 .with_client_info(ip_address, user_agent)
140 .with_error(e.clone());
141
142 let audit_logger = data.audit_logger.clone();
143 spawn(async move {
144 audit_logger.log(&audit_entry).await;
145 });
146
147 if e.contains("not found") {
148 HttpResponse::NotFound().json(serde_json::json!({
149 "error": e
150 }))
151 } else if e.contains("Unauthorized") {
152 HttpResponse::Forbidden().json(serde_json::json!({
153 "error": e
154 }))
155 } else if e.contains("anonymized") {
156 HttpResponse::Gone().json(serde_json::json!({
157 "error": e
158 }))
159 } else {
160 HttpResponse::InternalServerError().json(serde_json::json!({
161 "error": format!("Failed to export user data: {}", e)
162 }))
163 }
164 }
165 }
166}
167
168#[utoipa::path(
183 delete,
184 path = "/gdpr/erase",
185 tag = "GDPR",
186 summary = "Erase user data by anonymization (Article 17 - Right to Erasure)",
187 responses(
188 (status = 200, description = "User data anonymized"),
189 (status = 401, description = "Unauthorized"),
190 (status = 403, description = "Forbidden"),
191 (status = 404, description = "User not found"),
192 (status = 409, description = "Legal holds prevent erasure"),
193 (status = 410, description = "User already anonymized"),
194 (status = 500, description = "Internal server error"),
195 ),
196 security(("bearer_auth" = []))
197)]
198#[delete("/gdpr/erase")]
199pub async fn erase_user_data(
200 req: HttpRequest,
201 data: web::Data<AppState>,
202 auth: AuthenticatedUser,
203) -> impl Responder {
204 let user_id = auth.user_id;
206
207 let ip_address = extract_ip_address(&req);
209 let user_agent = extract_user_agent(&req);
210
211 let organization_id = if auth.role == "superadmin" {
213 None
214 } else {
215 auth.organization_id
216 };
217
218 match data
220 .gdpr_use_cases
221 .erase_user_data(user_id, user_id, organization_id)
222 .await
223 {
224 Ok(erase_response) => {
225 let user_email = erase_response.user_email.clone();
227 let user_name = format!(
228 "{} {}",
229 erase_response.user_first_name, erase_response.user_last_name
230 );
231 let owners_count = erase_response.owners_anonymized;
232
233 let audit_entry = AuditLogEntry::new(
235 AuditEventType::GdprDataErased,
236 Some(user_id),
237 organization_id,
238 )
239 .with_resource("User", user_id)
240 .with_client_info(ip_address, user_agent)
241 .with_metadata(serde_json::json!({
242 "owners_anonymized": erase_response.owners_anonymized,
243 "anonymized_at": erase_response.anonymized_at
244 }));
245
246 let audit_logger = data.audit_logger.clone();
247 spawn(async move {
248 audit_logger.log(&audit_entry).await;
249 });
250
251 let email_service = data.email_service.clone();
253 spawn(async move {
254 if let Err(e) = email_service
255 .send_gdpr_erasure_notification(&user_email, &user_name, owners_count)
256 .await
257 {
258 log::error!("Failed to send GDPR erasure email notification: {}", e);
259 }
260 });
261
262 HttpResponse::Ok().json(erase_response)
263 }
264 Err(e) => {
265 let audit_entry = AuditLogEntry::new(
267 AuditEventType::GdprDataErasureFailed,
268 Some(user_id),
269 organization_id,
270 )
271 .with_resource("User", user_id)
272 .with_client_info(ip_address, user_agent)
273 .with_error(e.clone());
274
275 let audit_logger = data.audit_logger.clone();
276 spawn(async move {
277 audit_logger.log(&audit_entry).await;
278 });
279
280 if e.contains("Unauthorized") {
281 HttpResponse::Forbidden().json(serde_json::json!({
282 "error": e
283 }))
284 } else if e.contains("already anonymized") {
285 HttpResponse::Gone().json(serde_json::json!({
286 "error": e
287 }))
288 } else if e.contains("legal holds") {
289 HttpResponse::Conflict().json(serde_json::json!({
290 "error": e,
291 "message": "Cannot erase data due to legal obligations. Please resolve pending issues before requesting erasure."
292 }))
293 } else if e.contains("not found") {
294 HttpResponse::NotFound().json(serde_json::json!({
295 "error": e
296 }))
297 } else {
298 HttpResponse::InternalServerError().json(serde_json::json!({
299 "error": format!("Failed to erase user data: {}", e)
300 }))
301 }
302 }
303 }
304}
305
306#[utoipa::path(
314 get,
315 path = "/gdpr/can-erase",
316 tag = "GDPR",
317 summary = "Check if user data can be erased (no legal holds)",
318 responses(
319 (status = 200, description = "Erasure eligibility status returned"),
320 (status = 401, description = "Unauthorized"),
321 (status = 500, description = "Internal server error"),
322 ),
323 security(("bearer_auth" = []))
324)]
325#[get("/gdpr/can-erase")]
326pub async fn can_erase_user(
327 req: HttpRequest,
328 data: web::Data<AppState>,
329 auth: AuthenticatedUser,
330) -> impl Responder {
331 let user_id = auth.user_id;
332
333 let ip_address = extract_ip_address(&req);
335 let user_agent = extract_user_agent(&req);
336
337 match data.gdpr_use_cases.can_erase_user(user_id).await {
338 Ok(can_erase) => {
339 let audit_entry = AuditLogEntry::new(
341 AuditEventType::GdprErasureCheckRequested,
342 Some(user_id),
343 auth.organization_id,
344 )
345 .with_resource("User", user_id)
346 .with_client_info(ip_address, user_agent)
347 .with_metadata(serde_json::json!({
348 "can_erase": can_erase
349 }));
350
351 let audit_logger = data.audit_logger.clone();
352 spawn(async move {
353 audit_logger.log(&audit_entry).await;
354 });
355
356 HttpResponse::Ok().json(serde_json::json!({
357 "can_erase": can_erase,
358 "user_id": user_id.to_string()
359 }))
360 }
361 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
362 "error": format!("Failed to check erasure eligibility: {}", e)
363 })),
364 }
365}
366
367#[utoipa::path(
389 put,
390 path = "/gdpr/rectify",
391 tag = "GDPR",
392 summary = "Rectify user personal data (Article 16 - Right to Rectification)",
393 request_body = GdprRectifyRequest,
394 responses(
395 (status = 200, description = "Data successfully rectified"),
396 (status = 400, description = "Validation error"),
397 (status = 401, description = "Unauthorized"),
398 (status = 403, description = "Forbidden"),
399 (status = 404, description = "User not found"),
400 (status = 500, description = "Internal server error"),
401 ),
402 security(("bearer_auth" = []))
403)]
404#[put("/gdpr/rectify")]
405pub async fn rectify_user_data(
406 req: HttpRequest,
407 data: web::Data<AppState>,
408 auth: AuthenticatedUser,
409 request: web::Json<GdprRectifyRequest>,
410) -> impl Responder {
411 let user_id = auth.user_id;
412
413 let ip_address = extract_ip_address(&req);
415 let user_agent = extract_user_agent(&req);
416
417 match data
419 .gdpr_use_cases
420 .rectify_user_data(
421 user_id,
422 user_id, request.email.clone(),
424 request.first_name.clone(),
425 request.last_name.clone(),
426 )
427 .await
428 {
429 Ok(_) => {
430 let audit_entry = AuditLogEntry::new(
432 AuditEventType::GdprDataRectified,
433 Some(user_id),
434 auth.organization_id,
435 )
436 .with_resource("User", user_id)
437 .with_client_info(ip_address, user_agent)
438 .with_metadata(serde_json::json!({
439 "fields_updated": {
440 "email": request.email.is_some(),
441 "first_name": request.first_name.is_some(),
442 "last_name": request.last_name.is_some()
443 }
444 }));
445
446 let audit_logger = data.audit_logger.clone();
447 spawn(async move {
448 audit_logger.log(&audit_entry).await;
449 });
450
451 let response = GdprActionResponse {
452 success: true,
453 message: "Personal data successfully rectified".to_string(),
454 updated_at: Utc::now().to_rfc3339(),
455 };
456
457 HttpResponse::Ok().json(response)
458 }
459 Err(e) => {
460 let audit_entry = AuditLogEntry::new(
462 AuditEventType::GdprDataRectificationFailed,
463 Some(user_id),
464 auth.organization_id,
465 )
466 .with_resource("User", user_id)
467 .with_client_info(ip_address, user_agent)
468 .with_error(e.clone());
469
470 let audit_logger = data.audit_logger.clone();
471 spawn(async move {
472 audit_logger.log(&audit_entry).await;
473 });
474
475 if e.contains("Unauthorized") {
476 HttpResponse::Forbidden().json(serde_json::json!({
477 "error": e
478 }))
479 } else if e.contains("not found") {
480 HttpResponse::NotFound().json(serde_json::json!({
481 "error": e
482 }))
483 } else if e.contains("Validation error")
484 || e.contains("Invalid email")
485 || e.contains("cannot be empty")
486 || e.contains("No fields provided")
487 {
488 HttpResponse::BadRequest().json(serde_json::json!({
489 "error": e
490 }))
491 } else {
492 HttpResponse::InternalServerError().json(serde_json::json!({
493 "error": format!("Failed to rectify user data: {}", e)
494 }))
495 }
496 }
497 }
498}
499
500#[utoipa::path(
517 put,
518 path = "/gdpr/restrict-processing",
519 tag = "GDPR",
520 summary = "Restrict data processing (Article 18 - Right to Restriction)",
521 request_body = GdprRestrictProcessingRequest,
522 responses(
523 (status = 200, description = "Processing restriction applied"),
524 (status = 400, description = "Processing already restricted"),
525 (status = 401, description = "Unauthorized"),
526 (status = 403, description = "Forbidden"),
527 (status = 404, description = "User not found"),
528 (status = 500, description = "Internal server error"),
529 ),
530 security(("bearer_auth" = []))
531)]
532#[put("/gdpr/restrict-processing")]
533pub async fn restrict_user_processing(
534 req: HttpRequest,
535 data: web::Data<AppState>,
536 auth: AuthenticatedUser,
537 _request: web::Json<GdprRestrictProcessingRequest>,
538) -> impl Responder {
539 let user_id = auth.user_id;
540
541 let ip_address = extract_ip_address(&req);
543 let user_agent = extract_user_agent(&req);
544
545 match data
547 .gdpr_use_cases
548 .restrict_user_processing(user_id, user_id)
549 .await
550 {
551 Ok(_) => {
552 let audit_entry = AuditLogEntry::new(
554 AuditEventType::GdprProcessingRestricted,
555 Some(user_id),
556 auth.organization_id,
557 )
558 .with_resource("User", user_id)
559 .with_client_info(ip_address, user_agent);
560
561 let audit_logger = data.audit_logger.clone();
562 spawn(async move {
563 audit_logger.log(&audit_entry).await;
564 });
565
566 let response = GdprActionResponse {
567 success: true,
568 message: "Data processing successfully restricted. Your data will be stored but not processed for certain operations.".to_string(),
569 updated_at: Utc::now().to_rfc3339(),
570 };
571
572 HttpResponse::Ok().json(response)
573 }
574 Err(e) => {
575 let audit_entry = AuditLogEntry::new(
577 AuditEventType::GdprProcessingRestrictionFailed,
578 Some(user_id),
579 auth.organization_id,
580 )
581 .with_resource("User", user_id)
582 .with_client_info(ip_address, user_agent)
583 .with_error(e.clone());
584
585 let audit_logger = data.audit_logger.clone();
586 spawn(async move {
587 audit_logger.log(&audit_entry).await;
588 });
589
590 if e.contains("Unauthorized") {
591 HttpResponse::Forbidden().json(serde_json::json!({
592 "error": e
593 }))
594 } else if e.contains("not found") {
595 HttpResponse::NotFound().json(serde_json::json!({
596 "error": e
597 }))
598 } else if e.contains("already restricted") {
599 HttpResponse::BadRequest().json(serde_json::json!({
600 "error": e
601 }))
602 } else {
603 HttpResponse::InternalServerError().json(serde_json::json!({
604 "error": format!("Failed to restrict processing: {}", e)
605 }))
606 }
607 }
608 }
609}
610
611#[utoipa::path(
630 put,
631 path = "/gdpr/marketing-preference",
632 tag = "GDPR",
633 summary = "Set marketing opt-out preference (Article 21 - Right to Object)",
634 request_body = GdprMarketingPreferenceRequest,
635 responses(
636 (status = 200, description = "Marketing preference updated"),
637 (status = 401, description = "Unauthorized"),
638 (status = 403, description = "Forbidden"),
639 (status = 404, description = "User not found"),
640 (status = 500, description = "Internal server error"),
641 ),
642 security(("bearer_auth" = []))
643)]
644#[put("/gdpr/marketing-preference")]
645pub async fn set_marketing_preference(
646 req: HttpRequest,
647 data: web::Data<AppState>,
648 auth: AuthenticatedUser,
649 request: web::Json<GdprMarketingPreferenceRequest>,
650) -> impl Responder {
651 let user_id = auth.user_id;
652
653 let ip_address = extract_ip_address(&req);
655 let user_agent = extract_user_agent(&req);
656
657 let opt_out = request.opt_out;
658
659 match data
661 .gdpr_use_cases
662 .set_marketing_preference(user_id, user_id, opt_out)
663 .await
664 {
665 Ok(_) => {
666 let event_type = if opt_out {
668 AuditEventType::GdprMarketingOptOut
669 } else {
670 AuditEventType::GdprMarketingOptIn
671 };
672
673 let audit_entry = AuditLogEntry::new(event_type, Some(user_id), auth.organization_id)
674 .with_resource("User", user_id)
675 .with_client_info(ip_address, user_agent)
676 .with_metadata(serde_json::json!({
677 "opt_out": opt_out
678 }));
679
680 let audit_logger = data.audit_logger.clone();
681 spawn(async move {
682 audit_logger.log(&audit_entry).await;
683 });
684
685 let message = if opt_out {
686 "You have successfully opted out of marketing communications. You will no longer receive promotional emails or offers."
687 } else {
688 "You have successfully opted back in to marketing communications. You will receive promotional emails and offers."
689 };
690
691 let response = GdprActionResponse {
692 success: true,
693 message: message.to_string(),
694 updated_at: Utc::now().to_rfc3339(),
695 };
696
697 HttpResponse::Ok().json(response)
698 }
699 Err(e) => {
700 let audit_entry = AuditLogEntry::new(
702 AuditEventType::GdprMarketingPreferenceChangeFailed,
703 Some(user_id),
704 auth.organization_id,
705 )
706 .with_resource("User", user_id)
707 .with_client_info(ip_address, user_agent)
708 .with_error(e.clone());
709
710 let audit_logger = data.audit_logger.clone();
711 spawn(async move {
712 audit_logger.log(&audit_entry).await;
713 });
714
715 if e.contains("Unauthorized") {
716 HttpResponse::Forbidden().json(serde_json::json!({
717 "error": e
718 }))
719 } else if e.contains("not found") {
720 HttpResponse::NotFound().json(serde_json::json!({
721 "error": e
722 }))
723 } else {
724 HttpResponse::InternalServerError().json(serde_json::json!({
725 "error": format!("Failed to set marketing preference: {}", e)
726 }))
727 }
728 }
729 }
730}
731
732#[cfg(test)]
733mod tests {
734 #[test]
738 fn test_handler_structure_export() {
739 }
742
743 #[test]
744 fn test_handler_structure_erase() {
745 }
747
748 #[test]
749 fn test_handler_structure_can_erase() {
750 }
752}