cdp-edge 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +367 -0
- package/bin/cdp-edge.js +61 -0
- package/contracts/api-versions.json +368 -0
- package/dist/commands/analyze.js +52 -0
- package/dist/commands/infra.js +54 -0
- package/dist/commands/install.js +168 -0
- package/dist/commands/server.js +174 -0
- package/dist/commands/setup.js +123 -0
- package/dist/commands/validate.js +84 -0
- package/dist/index.js +12 -0
- package/docs/CI-CD-SETUP.md +217 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/docs/events-reference.md +359 -0
- package/docs/installation.md +155 -0
- package/docs/quick-start.md +185 -0
- package/docs/sdk-reference.md +371 -0
- package/docs/whatsapp-ctwa.md +209 -0
- package/extracted-skill/tracking-events-generator/INDEX.md +94 -0
- package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +58 -0
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +594 -0
- package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +412 -0
- package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +333 -0
- package/extracted-skill/tracking-events-generator/SKILL.md +257 -0
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -0
- package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +54 -0
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +1304 -0
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +264 -0
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +149 -0
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +2077 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +1419 -0
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +456 -0
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +667 -0
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1455 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +224 -0
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +61 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +52 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +109 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +365 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +643 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +62 -0
- package/extracted-skill/tracking-events-generator/agents/localization-agent.md +55 -0
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +59 -0
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +900 -0
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +1922 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.json +109 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +703 -0
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +110 -0
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +255 -0
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +1157 -0
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +1432 -0
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +310 -0
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +849 -0
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +250 -0
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +313 -0
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1752 -0
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1188 -0
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +383 -0
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +111 -0
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +364 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +267 -0
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +69 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +699 -0
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +422 -0
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -0
- package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -0
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +368 -0
- package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +107 -0
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -0
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -0
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -0
- package/extracted-skill/tracking-events-generator/knowledge-base.md +2894 -0
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -0
- package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +78 -0
- package/extracted-skill/tracking-events-generator/models/captura-lead-evento-externo.md +99 -0
- package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +111 -0
- package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +672 -0
- package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +55 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -0
- package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +68 -0
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -0
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -0
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/real-estate-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/sales-page-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/trafego-direto.md +582 -0
- package/extracted-skill/tracking-events-generator/models/webinar-registration.md +63 -0
- package/extracted-skill/tracking-events-generator/tracking.config.js +46 -0
- package/extracted-skill/tracking-events-generator/walkthrough.md +26 -0
- package/package.json +75 -0
- package/server-edge-tracker/INSTALAR.md +328 -0
- package/server-edge-tracker/migrate-new-db.sql +137 -0
- package/server-edge-tracker/migrate-v2.sql +16 -0
- package/server-edge-tracker/migrate-v3.sql +6 -0
- package/server-edge-tracker/migrate-v4.sql +18 -0
- package/server-edge-tracker/migrate-v5.sql +17 -0
- package/server-edge-tracker/migrate-v6.sql +24 -0
- package/server-edge-tracker/migrate.sql +111 -0
- package/server-edge-tracker/schema.sql +265 -0
- package/server-edge-tracker/worker.js +2574 -0
- package/server-edge-tracker/wrangler.toml +85 -0
- package/templates/afiliado-sem-landing.md +312 -0
- package/templates/captura-de-lead.md +78 -0
- package/templates/captura-lead-evento-externo.md +99 -0
- package/templates/checkout-proprio.md +111 -0
- package/templates/install/.claude/commands/cdp.md +1 -0
- package/templates/install/CLAUDE.md +65 -0
- package/templates/linkedin/tag-template.js +46 -0
- package/templates/multi-step-checkout.md +673 -0
- package/templates/pagina-obrigado.md +55 -0
- package/templates/pinterest/conversions-api-template.js +144 -0
- package/templates/pinterest/event-mappings.json +48 -0
- package/templates/pinterest/tag-template.js +28 -0
- package/templates/quiz-funnel.md +68 -0
- package/templates/reddit/conversions-api-template.js +205 -0
- package/templates/reddit/event-mappings.json +56 -0
- package/templates/reddit/pixel-template.js +46 -0
- package/templates/scenarios/behavior-engine.js +402 -0
- package/templates/scenarios/real-estate-logic.md +50 -0
- package/templates/scenarios/sales-page-logic.md +50 -0
- package/templates/spotify/pixel-template.js +46 -0
- package/templates/trafego-direto.md +582 -0
- package/templates/vsl-page.md +292 -0
- package/templates/webinar-registration.md +63 -0
|
@@ -0,0 +1,2077 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compliance-agent
|
|
3
|
+
description: Compliance Enterprise Agent - GDPR/LGPD/CCPA compliance, consent management, data rights for CDP Edge
|
|
4
|
+
type: agent
|
|
5
|
+
persona: Compliance Officer specializing in data privacy regulations (GDPR, LGPD, CCPA) and consent management systems
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Compliance Enterprise Agent
|
|
10
|
+
|
|
11
|
+
## 🛡️ Visão Geral
|
|
12
|
+
|
|
13
|
+
Agente especializado em compliance de dados para o sistema CDP Edge. Implementa funcionalidades completas de GDPR (União Europeia), LGPD (Brasil) e CCPA (Califórnia), incluindo gestão de consentimento, direitos dos titulares, políticas de retenção e trilhas de auditoria.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📚 Regulamentações Suportadas
|
|
18
|
+
|
|
19
|
+
### GDPR (General Data Protection Regulation - EU)
|
|
20
|
+
- **Bases Legais**: Consentimento, Legítimo Interesse, Cumprimento de Contrato
|
|
21
|
+
- **Direitos dos Titulares**: Acesso, Retificação, Exclusão, Portabilidade, Oposição, Restrição
|
|
22
|
+
- **Consentimento**: Requer opt-in explícito, revogável a qualquer momento
|
|
23
|
+
- **Data Breach**: Notificação obrigatória em até 72h
|
|
24
|
+
- **DPO**: Ponto de contato obrigatório
|
|
25
|
+
|
|
26
|
+
### LGPD (Lei Geral de Proteção de Dados - Brasil)
|
|
27
|
+
- **Bases Legais**: Consentimento, Legítimo Interesse, Cumprimento de Obrigação Legal, etc.
|
|
28
|
+
- **Direitos dos Titulares**: Confirmação, Acesso, Correção, Eliminação, Portabilidade, Revogação
|
|
29
|
+
- **ANPD**: Autoridade Nacional de Proteção de Dados
|
|
30
|
+
- **Consentimento**: Livre, informado e inequívoco
|
|
31
|
+
- **Data Breach**: Notificação à ANPD e titulares em até 2 dias úteis
|
|
32
|
+
|
|
33
|
+
### CCPA (California Consumer Privacy Act - USA)
|
|
34
|
+
- **Direitos do Consumidor**: Acesso, Exclusão, Opt-out de Venda, Não Discriminação
|
|
35
|
+
- **Opt-out**: Botão "Do Not Sell My Personal Information"
|
|
36
|
+
- **Exceções**: Necessário para negócio, segurança, pesquisa
|
|
37
|
+
- **Private Right of Action**: Ações privadas por violações
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 🏗️ Arquitetura de Compliance
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ Consent Management Layer │
|
|
46
|
+
│ (Consent collection, storage, tracking) │
|
|
47
|
+
└─────────────────────────────────────────────────────────────┘
|
|
48
|
+
↓
|
|
49
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
50
|
+
│ Data Rights Layer │
|
|
51
|
+
│ (Access, Deletion, Portability, Rectification) │
|
|
52
|
+
└─────────────────────────────────────────────────────────────┘
|
|
53
|
+
↓
|
|
54
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ Data Retention Layer │
|
|
56
|
+
│ (TTL policies, auto-deletion, archival) │
|
|
57
|
+
└─────────────────────────────────────────────────────────────┘
|
|
58
|
+
↓
|
|
59
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
60
|
+
│ Compliance Audit Layer │
|
|
61
|
+
│ (Audit trails, consent history, access logs) │
|
|
62
|
+
└─────────────────────────────────────────────────────────────┘
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🍪 Sistema de Gestão de Consentimento
|
|
68
|
+
|
|
69
|
+
### D1 Schema para Consent Management
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- Consent Records Table
|
|
73
|
+
CREATE TABLE IF NOT EXISTS consent_records (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
user_id TEXT NOT NULL,
|
|
76
|
+
consent_id TEXT UNIQUE NOT NULL,
|
|
77
|
+
consent_type TEXT NOT NULL, -- 'marketing', 'analytics', 'personalization', 'essential'
|
|
78
|
+
consent_given BOOLEAN NOT NULL,
|
|
79
|
+
legal_basis TEXT NOT NULL, -- 'consent', 'legitimate_interest', 'contract', 'legal_obligation'
|
|
80
|
+
timestamp DATETIME NOT NULL,
|
|
81
|
+
ip_address TEXT,
|
|
82
|
+
user_agent TEXT,
|
|
83
|
+
consent_version TEXT NOT NULL, -- Version of consent policy
|
|
84
|
+
source TEXT NOT NULL, -- 'cookie_banner', 'preference_center', 'explicit_action'
|
|
85
|
+
expires_at DATETIME, -- Optional expiration for consent
|
|
86
|
+
UNIQUE(user_id, consent_type, consent_id)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_consent_user_id
|
|
90
|
+
ON consent_records(user_id, timestamp DESC);
|
|
91
|
+
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_consent_id
|
|
93
|
+
ON consent_records(consent_id);
|
|
94
|
+
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_consent_type
|
|
96
|
+
ON consent_records(consent_type, consent_given);
|
|
97
|
+
|
|
98
|
+
-- Consent History Table (Audit Trail)
|
|
99
|
+
CREATE TABLE IF NOT EXISTS consent_history (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
consent_id TEXT NOT NULL,
|
|
102
|
+
action TEXT NOT NULL, -- 'granted', 'revoked', 'updated'
|
|
103
|
+
previous_state JSON, -- Previous consent state
|
|
104
|
+
new_state JSON, -- New consent state
|
|
105
|
+
timestamp DATETIME NOT NULL,
|
|
106
|
+
ip_address TEXT,
|
|
107
|
+
user_agent TEXT,
|
|
108
|
+
reason TEXT -- Reason for change (e.g., 'user_action', 'policy_update')
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_consent_history_consent_id
|
|
112
|
+
ON consent_history(consent_id, timestamp DESC);
|
|
113
|
+
|
|
114
|
+
CREATE INDEX IF NOT EXISTS idx_consent_history_action
|
|
115
|
+
ON consent_history(action, timestamp DESC);
|
|
116
|
+
|
|
117
|
+
-- Cookie Preferences Table
|
|
118
|
+
CREATE TABLE IF NOT EXISTS cookie_preferences (
|
|
119
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
120
|
+
user_id TEXT UNIQUE,
|
|
121
|
+
session_id TEXT,
|
|
122
|
+
analytics_consent BOOLEAN DEFAULT FALSE,
|
|
123
|
+
marketing_consent BOOLEAN DEFAULT FALSE,
|
|
124
|
+
personalization_consent BOOLEAN DEFAULT FALSE,
|
|
125
|
+
necessary_consent BOOLEAN DEFAULT TRUE, -- Always true for essential cookies
|
|
126
|
+
preferences_updated DATETIME NOT NULL,
|
|
127
|
+
preference_source TEXT NOT NULL -- 'banner', 'preference_center', 'api'
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_cookie_preferences_user_id
|
|
131
|
+
ON cookie_preferences(user_id);
|
|
132
|
+
|
|
133
|
+
-- Data Retention Policies Table
|
|
134
|
+
CREATE TABLE IF NOT EXISTS retention_policies (
|
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
136
|
+
data_type TEXT NOT NULL UNIQUE,
|
|
137
|
+
retention_period_days INTEGER NOT NULL,
|
|
138
|
+
legal_basis TEXT NOT NULL,
|
|
139
|
+
policy_name TEXT NOT NULL,
|
|
140
|
+
description TEXT,
|
|
141
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
142
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
-- User Data Deletion Requests Table
|
|
146
|
+
CREATE TABLE IF NOT EXISTS deletion_requests (
|
|
147
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
148
|
+
user_id TEXT NOT NULL,
|
|
149
|
+
request_id TEXT UNIQUE NOT NULL,
|
|
150
|
+
request_type TEXT NOT NULL, -- 'deletion', 'rectification', 'portability', 'access'
|
|
151
|
+
status TEXT NOT NULL, -- 'pending', 'in_progress', 'completed', 'rejected'
|
|
152
|
+
requested_at DATETIME NOT NULL,
|
|
153
|
+
completed_at DATETIME,
|
|
154
|
+
rejection_reason TEXT,
|
|
155
|
+
ip_address TEXT,
|
|
156
|
+
user_agent TEXT,
|
|
157
|
+
processed_by TEXT, -- System or admin user
|
|
158
|
+
processing_notes TEXT
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
CREATE INDEX IF NOT EXISTS idx_deletion_requests_user_id
|
|
162
|
+
ON deletion_requests(user_id, requested_at DESC);
|
|
163
|
+
|
|
164
|
+
CREATE INDEX IF NOT EXISTS idx_deletion_requests_status
|
|
165
|
+
ON deletion_requests(status, requested_at);
|
|
166
|
+
|
|
167
|
+
-- Consent Policy Versions Table
|
|
168
|
+
CREATE TABLE IF NOT EXISTS consent_policy_versions (
|
|
169
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
170
|
+
version TEXT UNIQUE NOT NULL,
|
|
171
|
+
policy_text TEXT NOT NULL,
|
|
172
|
+
effective_date DATETIME NOT NULL,
|
|
173
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
CREATE INDEX IF NOT EXISTS idx_consent_policy_version
|
|
177
|
+
ON consent_policy_versions(version, effective_date DESC);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 🔐 Consent Management System
|
|
183
|
+
|
|
184
|
+
### Consent Collection & Storage
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
/**
|
|
188
|
+
* Consent Manager
|
|
189
|
+
* Manages user consent collection, storage, and tracking
|
|
190
|
+
*/
|
|
191
|
+
class ConsentManager {
|
|
192
|
+
constructor(env) {
|
|
193
|
+
this.db = env.DB;
|
|
194
|
+
this.currentPolicyVersion = '1.0.0'; // Current consent policy version
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Grant consent for a specific data processing purpose
|
|
199
|
+
* @param {string} userId - User ID
|
|
200
|
+
* @param {string} consentType - Type of consent (marketing, analytics, personalization, essential)
|
|
201
|
+
* @param {object} metadata - Metadata (IP, user-agent, source)
|
|
202
|
+
* @returns {Promise<object>} Consent record
|
|
203
|
+
*/
|
|
204
|
+
async grantConsent(userId, consentType, metadata = {}) {
|
|
205
|
+
const consentId = this.generateConsentId();
|
|
206
|
+
const now = new Date().toISOString();
|
|
207
|
+
const expiresAt = this.calculateExpiration(consentType);
|
|
208
|
+
|
|
209
|
+
// Check if consent already exists
|
|
210
|
+
const existingConsent = await this.getConsent(userId, consentType);
|
|
211
|
+
|
|
212
|
+
// Record previous state for audit trail
|
|
213
|
+
const previousState = existingConsent ? {
|
|
214
|
+
consent_given: existingConsent.consent_given,
|
|
215
|
+
legal_basis: existingConsent.legal_basis,
|
|
216
|
+
timestamp: existingConsent.timestamp
|
|
217
|
+
} : null;
|
|
218
|
+
|
|
219
|
+
// Create or update consent record
|
|
220
|
+
const consentRecord = {
|
|
221
|
+
consent_id: consentId,
|
|
222
|
+
consent_type: consentType,
|
|
223
|
+
consent_given: true,
|
|
224
|
+
legal_basis: 'consent',
|
|
225
|
+
timestamp: now,
|
|
226
|
+
ip_address: metadata.ip_address || null,
|
|
227
|
+
user_agent: metadata.user_agent || null,
|
|
228
|
+
consent_version: this.currentPolicyVersion,
|
|
229
|
+
source: metadata.source || 'explicit_action',
|
|
230
|
+
expires_at: expiresAt
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (existingConsent) {
|
|
234
|
+
// Update existing consent
|
|
235
|
+
await this.db.prepare(`
|
|
236
|
+
UPDATE consent_records
|
|
237
|
+
SET consent_given = ?, timestamp = ?, ip_address = ?,
|
|
238
|
+
user_agent = ?, consent_version = ?, source = ?, expires_at = ?
|
|
239
|
+
WHERE user_id = ? AND consent_type = ?
|
|
240
|
+
`).bind(
|
|
241
|
+
true, now, metadata.ip_address, metadata.user_agent,
|
|
242
|
+
this.currentPolicyVersion, metadata.source, expiresAt,
|
|
243
|
+
userId, consentType
|
|
244
|
+
).run();
|
|
245
|
+
} else {
|
|
246
|
+
// Insert new consent
|
|
247
|
+
await this.db.prepare(`
|
|
248
|
+
INSERT INTO consent_records
|
|
249
|
+
(user_id, consent_id, consent_type, consent_given, legal_basis,
|
|
250
|
+
timestamp, ip_address, user_agent, consent_version, source, expires_at)
|
|
251
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
252
|
+
`).bind(
|
|
253
|
+
userId, consentId, consentType, true, 'consent',
|
|
254
|
+
now, metadata.ip_address, metadata.user_agent,
|
|
255
|
+
this.currentPolicyVersion, metadata.source, expiresAt
|
|
256
|
+
).run();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Record in consent history
|
|
260
|
+
await this.recordConsentHistory(consentId, 'granted', previousState, consentRecord, metadata);
|
|
261
|
+
|
|
262
|
+
// Update cookie preferences
|
|
263
|
+
await this.updateCookiePreferences(userId, consentType, true, metadata.source);
|
|
264
|
+
|
|
265
|
+
return consentRecord;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Revoke consent for a specific data processing purpose
|
|
270
|
+
* @param {string} userId - User ID
|
|
271
|
+
* @param {string} consentType - Type of consent
|
|
272
|
+
* @param {object} metadata - Metadata (IP, user-agent, reason)
|
|
273
|
+
* @returns {Promise<object>} Consent record
|
|
274
|
+
*/
|
|
275
|
+
async revokeConsent(userId, consentType, metadata = {}) {
|
|
276
|
+
const now = new Date().toISOString();
|
|
277
|
+
|
|
278
|
+
// Get current consent state
|
|
279
|
+
const existingConsent = await this.getConsent(userId, consentType);
|
|
280
|
+
|
|
281
|
+
if (!existingConsent) {
|
|
282
|
+
throw new Error('No consent found to revoke');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const previousState = {
|
|
286
|
+
consent_given: existingConsent.consent_given,
|
|
287
|
+
legal_basis: existingConsent.legal_basis,
|
|
288
|
+
timestamp: existingConsent.timestamp
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Update consent record
|
|
292
|
+
await this.db.prepare(`
|
|
293
|
+
UPDATE consent_records
|
|
294
|
+
SET consent_given = ?, timestamp = ?, source = ?
|
|
295
|
+
WHERE user_id = ? AND consent_type = ?
|
|
296
|
+
`).bind(
|
|
297
|
+
false, now, metadata.source || 'user_action',
|
|
298
|
+
userId, consentType
|
|
299
|
+
).run();
|
|
300
|
+
|
|
301
|
+
// Record in consent history
|
|
302
|
+
await this.recordConsentHistory(
|
|
303
|
+
existingConsent.consent_id,
|
|
304
|
+
'revoked',
|
|
305
|
+
previousState,
|
|
306
|
+
{ ...existingConsent, consent_given: false, timestamp: now },
|
|
307
|
+
metadata
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Update cookie preferences
|
|
311
|
+
await this.updateCookiePreferences(userId, consentType, false, metadata.source);
|
|
312
|
+
|
|
313
|
+
// Trigger data deletion for affected data (if required)
|
|
314
|
+
await this.handleConsentRevocation(userId, consentType);
|
|
315
|
+
|
|
316
|
+
return { consent_id: existingConsent.consent_id, consent_given: false, timestamp: now };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get current consent for a user and consent type
|
|
321
|
+
* @param {string} userId - User ID
|
|
322
|
+
* @param {string} consentType - Type of consent
|
|
323
|
+
* @returns {Promise<object|null>} Consent record or null
|
|
324
|
+
*/
|
|
325
|
+
async getConsent(userId, consentType) {
|
|
326
|
+
const result = await this.db.prepare(`
|
|
327
|
+
SELECT * FROM consent_records
|
|
328
|
+
WHERE user_id = ? AND consent_type = ?
|
|
329
|
+
ORDER BY timestamp DESC
|
|
330
|
+
LIMIT 1
|
|
331
|
+
`).bind(userId, consentType).first();
|
|
332
|
+
|
|
333
|
+
return result || null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get all consents for a user
|
|
338
|
+
* @param {string} userId - User ID
|
|
339
|
+
* @returns {Promise<Array>} All consent records
|
|
340
|
+
*/
|
|
341
|
+
async getAllConsents(userId) {
|
|
342
|
+
const results = await this.db.prepare(`
|
|
343
|
+
SELECT * FROM consent_records
|
|
344
|
+
WHERE user_id = ?
|
|
345
|
+
ORDER BY timestamp DESC
|
|
346
|
+
`).bind(userId).all();
|
|
347
|
+
|
|
348
|
+
return results;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Check if user has valid consent for a specific purpose
|
|
353
|
+
* @param {string} userId - User ID
|
|
354
|
+
* @param {string} consentType - Type of consent
|
|
355
|
+
* @returns {Promise<boolean>} True if consent is valid
|
|
356
|
+
*/
|
|
357
|
+
async hasValidConsent(userId, consentType) {
|
|
358
|
+
const consent = await this.getConsent(userId, consentType);
|
|
359
|
+
|
|
360
|
+
if (!consent) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Check if consent is still valid (not revoked and not expired)
|
|
365
|
+
if (!consent.consent_given) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check expiration
|
|
370
|
+
if (consent.expires_at && new Date(consent.expires_at) < new Date()) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Update cookie preferences for a user
|
|
379
|
+
* @param {string} userId - User ID
|
|
380
|
+
* @param {string} consentType - Type of consent
|
|
381
|
+
* @param {boolean} value - Consent value
|
|
382
|
+
* @param {string} source - Source of preference change
|
|
383
|
+
*/
|
|
384
|
+
async updateCookiePreferences(userId, consentType, value, source) {
|
|
385
|
+
const now = new Date().toISOString();
|
|
386
|
+
|
|
387
|
+
// Map consent types to cookie preference columns
|
|
388
|
+
const consentTypeMap = {
|
|
389
|
+
'marketing': 'marketing_consent',
|
|
390
|
+
'analytics': 'analytics_consent',
|
|
391
|
+
'personalization': 'personalization_consent',
|
|
392
|
+
'essential': 'necessary_consent'
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const column = consentTypeMap[consentType];
|
|
396
|
+
if (!column) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check if preferences exist
|
|
401
|
+
const existing = await this.db.prepare(`
|
|
402
|
+
SELECT * FROM cookie_preferences WHERE user_id = ?
|
|
403
|
+
`).bind(userId).first();
|
|
404
|
+
|
|
405
|
+
if (existing) {
|
|
406
|
+
// Update existing preferences
|
|
407
|
+
await this.db.prepare(`
|
|
408
|
+
UPDATE cookie_preferences
|
|
409
|
+
SET ${column} = ?, preferences_updated = ?, preference_source = ?
|
|
410
|
+
WHERE user_id = ?
|
|
411
|
+
`).bind(value, now, source, userId).run();
|
|
412
|
+
} else {
|
|
413
|
+
// Insert new preferences
|
|
414
|
+
await this.db.prepare(`
|
|
415
|
+
INSERT INTO cookie_preferences
|
|
416
|
+
(user_id, session_id, analytics_consent, marketing_consent,
|
|
417
|
+
personalization_consent, necessary_consent, preferences_updated, preference_source)
|
|
418
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
419
|
+
`).bind(
|
|
420
|
+
userId, null,
|
|
421
|
+
consentType === 'analytics' ? value : false,
|
|
422
|
+
consentType === 'marketing' ? value : false,
|
|
423
|
+
consentType === 'personalization' ? value : false,
|
|
424
|
+
true, // necessary_consent is always true
|
|
425
|
+
now, source
|
|
426
|
+
).run();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get cookie preferences for a user
|
|
432
|
+
* @param {string} userId - User ID
|
|
433
|
+
* @returns {Promise<object>} Cookie preferences
|
|
434
|
+
*/
|
|
435
|
+
async getCookiePreferences(userId) {
|
|
436
|
+
const result = await this.db.prepare(`
|
|
437
|
+
SELECT * FROM cookie_preferences WHERE user_id = ?
|
|
438
|
+
`).bind(userId).first();
|
|
439
|
+
|
|
440
|
+
return result || {
|
|
441
|
+
analytics_consent: false,
|
|
442
|
+
marketing_consent: false,
|
|
443
|
+
personalization_consent: false,
|
|
444
|
+
necessary_consent: true
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Record consent history for audit trail
|
|
450
|
+
* @param {string} consentId - Consent ID
|
|
451
|
+
* @param {string} action - Action (granted, revoked, updated)
|
|
452
|
+
* @param {object} previousState - Previous consent state
|
|
453
|
+
* @param {object} newState - New consent state
|
|
454
|
+
* @param {object} metadata - Metadata
|
|
455
|
+
*/
|
|
456
|
+
async recordConsentHistory(consentId, action, previousState, newState, metadata) {
|
|
457
|
+
await this.db.prepare(`
|
|
458
|
+
INSERT INTO consent_history
|
|
459
|
+
(consent_id, action, previous_state, new_state, timestamp, ip_address, user_agent, reason)
|
|
460
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
461
|
+
`).bind(
|
|
462
|
+
consentId,
|
|
463
|
+
action,
|
|
464
|
+
JSON.stringify(previousState),
|
|
465
|
+
JSON.stringify(newState),
|
|
466
|
+
new Date().toISOString(),
|
|
467
|
+
metadata.ip_address || null,
|
|
468
|
+
metadata.user_agent || null,
|
|
469
|
+
metadata.reason || null
|
|
470
|
+
).run();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Calculate expiration date for consent
|
|
475
|
+
* @param {string} consentType - Type of consent
|
|
476
|
+
* @returns {string|null} ISO date string or null
|
|
477
|
+
*/
|
|
478
|
+
calculateExpiration(consentType) {
|
|
479
|
+
const expirationPeriods = {
|
|
480
|
+
'marketing': 365 * 2, // 2 years
|
|
481
|
+
'analytics': 365 * 1, // 1 year
|
|
482
|
+
'personalization': 365 * 1, // 1 year
|
|
483
|
+
'essential': null // No expiration
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const days = expirationPeriods[consentType];
|
|
487
|
+
if (!days) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const expiresAt = new Date();
|
|
492
|
+
expiresAt.setDate(expiresAt.getDate() + days);
|
|
493
|
+
return expiresAt.toISOString();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Generate unique consent ID
|
|
498
|
+
* @returns {string} Consent ID
|
|
499
|
+
*/
|
|
500
|
+
generateConsentId() {
|
|
501
|
+
return `consent_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Handle consent revocation (trigger data deletion if required)
|
|
506
|
+
* @param {string} userId - User ID
|
|
507
|
+
* @param {string} consentType - Type of consent revoked
|
|
508
|
+
*/
|
|
509
|
+
async handleConsentRevocation(userId, consentType) {
|
|
510
|
+
// For marketing consent revocation, remove PII from marketing databases
|
|
511
|
+
if (consentType === 'marketing') {
|
|
512
|
+
// Remove user from marketing campaigns
|
|
513
|
+
await this.removeFromMarketingLists(userId);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// For analytics consent revocation, anonymize analytics data
|
|
517
|
+
if (consentType === 'analytics') {
|
|
518
|
+
// Anonymize user analytics data
|
|
519
|
+
await this.anonymizeAnalyticsData(userId);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Remove user from marketing lists
|
|
525
|
+
* @param {string} userId - User ID
|
|
526
|
+
*/
|
|
527
|
+
async removeFromMarketingLists(userId) {
|
|
528
|
+
// Update user journeys to mark for marketing exclusion
|
|
529
|
+
await this.db.prepare(`
|
|
530
|
+
UPDATE user_journeys
|
|
531
|
+
SET marketing_consent = false
|
|
532
|
+
WHERE user_id = ?
|
|
533
|
+
`).bind(userId).run();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Anonymize analytics data for user
|
|
538
|
+
* @param {string} userId - User ID
|
|
539
|
+
*/
|
|
540
|
+
async anonymizeAnalyticsData(userId) {
|
|
541
|
+
// Anonymize user_id in user_journeys (replace with hash)
|
|
542
|
+
const anonymizedId = `anon_${crypto.randomUUID()}`;
|
|
543
|
+
|
|
544
|
+
await this.db.prepare(`
|
|
545
|
+
UPDATE user_journeys
|
|
546
|
+
SET user_id = ?, anonymized = true
|
|
547
|
+
WHERE user_id = ?
|
|
548
|
+
`).bind(anonymizedId, userId).run();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Global consent manager instance
|
|
553
|
+
let consentManager = null;
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## 👤 Data Rights Management
|
|
559
|
+
|
|
560
|
+
### GDPR/LGPD/CCPA Data Rights Implementation
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
/**
|
|
564
|
+
* Data Rights Manager
|
|
565
|
+
* Handles GDPR/LGPD/CCPA data rights: access, deletion, portability, rectification
|
|
566
|
+
*/
|
|
567
|
+
class DataRightsManager {
|
|
568
|
+
constructor(env) {
|
|
569
|
+
this.db = env.DB;
|
|
570
|
+
this.complianceConfig = {
|
|
571
|
+
gdpr: {
|
|
572
|
+
accessResponseTime: 30, // days
|
|
573
|
+
deletionResponseTime: 30, // days
|
|
574
|
+
portabilityResponseTime: 30 // days
|
|
575
|
+
},
|
|
576
|
+
lgpd: {
|
|
577
|
+
accessResponseTime: 15, // days
|
|
578
|
+
deletionResponseTime: 15, // days
|
|
579
|
+
portabilityResponseTime: 15 // days
|
|
580
|
+
},
|
|
581
|
+
ccpa: {
|
|
582
|
+
accessResponseTime: 45, // days
|
|
583
|
+
deletionResponseTime: 45, // days
|
|
584
|
+
optOutResponseTime: 10 // days
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Submit data access request (GDPR Art. 15, LGPD Art. 18, CCPA)
|
|
591
|
+
* @param {string} userId - User ID
|
|
592
|
+
* @param {object} metadata - Request metadata
|
|
593
|
+
* @returns {Promise<string>} Request ID
|
|
594
|
+
*/
|
|
595
|
+
async submitAccessRequest(userId, metadata = {}) {
|
|
596
|
+
const requestId = this.generateRequestId('access');
|
|
597
|
+
|
|
598
|
+
await this.db.prepare(`
|
|
599
|
+
INSERT INTO deletion_requests
|
|
600
|
+
(user_id, request_id, request_type, status, requested_at, ip_address, user_agent)
|
|
601
|
+
VALUES (?, ?, 'access', 'pending', ?, ?, ?)
|
|
602
|
+
`).bind(
|
|
603
|
+
userId,
|
|
604
|
+
requestId,
|
|
605
|
+
new Date().toISOString(),
|
|
606
|
+
metadata.ip_address || null,
|
|
607
|
+
metadata.user_agent || null
|
|
608
|
+
).run();
|
|
609
|
+
|
|
610
|
+
return requestId;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Process data access request
|
|
615
|
+
* @param {string} requestId - Request ID
|
|
616
|
+
* @returns {Promise<object>} User data package
|
|
617
|
+
*/
|
|
618
|
+
async processAccessRequest(requestId) {
|
|
619
|
+
const request = await this.db.prepare(`
|
|
620
|
+
SELECT * FROM deletion_requests WHERE request_id = ?
|
|
621
|
+
`).bind(requestId).first();
|
|
622
|
+
|
|
623
|
+
if (!request) {
|
|
624
|
+
throw new Error('Request not found');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (request.status !== 'pending') {
|
|
628
|
+
throw new Error('Request already processed');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Update status to in_progress
|
|
632
|
+
await this.db.prepare(`
|
|
633
|
+
UPDATE deletion_requests SET status = 'in_progress' WHERE request_id = ?
|
|
634
|
+
`).bind(requestId).run();
|
|
635
|
+
|
|
636
|
+
// Collect all user data
|
|
637
|
+
const userData = await this.collectUserData(request.user_id);
|
|
638
|
+
|
|
639
|
+
// Update status to completed
|
|
640
|
+
await this.db.prepare(`
|
|
641
|
+
UPDATE deletion_requests
|
|
642
|
+
SET status = 'completed', completed_at = ?, processed_by = 'system'
|
|
643
|
+
WHERE request_id = ?
|
|
644
|
+
`).bind(new Date().toISOString(), requestId).run();
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
request_id: requestId,
|
|
648
|
+
user_id: request.user_id,
|
|
649
|
+
data_collected_at: new Date().toISOString(),
|
|
650
|
+
data: userData
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Submit data deletion request (GDPR Art. 17, LGPD Art. 18, CCPA)
|
|
656
|
+
* @param {string} userId - User ID
|
|
657
|
+
* @param {string} reason - Reason for deletion (optional)
|
|
658
|
+
* @param {object} metadata - Request metadata
|
|
659
|
+
* @returns {Promise<string>} Request ID
|
|
660
|
+
*/
|
|
661
|
+
async submitDeletionRequest(userId, reason = '', metadata = {}) {
|
|
662
|
+
const requestId = this.generateRequestId('deletion');
|
|
663
|
+
|
|
664
|
+
await this.db.prepare(`
|
|
665
|
+
INSERT INTO deletion_requests
|
|
666
|
+
(user_id, request_id, request_type, status, requested_at,
|
|
667
|
+
ip_address, user_agent, processing_notes)
|
|
668
|
+
VALUES (?, ?, 'deletion', 'pending', ?, ?, ?, ?)
|
|
669
|
+
`).bind(
|
|
670
|
+
userId,
|
|
671
|
+
requestId,
|
|
672
|
+
new Date().toISOString(),
|
|
673
|
+
metadata.ip_address || null,
|
|
674
|
+
metadata.user_agent || null,
|
|
675
|
+
reason
|
|
676
|
+
).run();
|
|
677
|
+
|
|
678
|
+
return requestId;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Process data deletion request
|
|
683
|
+
* @param {string} requestId - Request ID
|
|
684
|
+
* @returns {Promise<object>} Deletion result
|
|
685
|
+
*/
|
|
686
|
+
async processDeletionRequest(requestId) {
|
|
687
|
+
const request = await this.db.prepare(`
|
|
688
|
+
SELECT * FROM deletion_requests WHERE request_id = ?
|
|
689
|
+
`).bind(requestId).first();
|
|
690
|
+
|
|
691
|
+
if (!request) {
|
|
692
|
+
throw new Error('Request not found');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (request.status !== 'pending') {
|
|
696
|
+
throw new Error('Request already processed');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Update status to in_progress
|
|
700
|
+
await this.db.prepare(`
|
|
701
|
+
UPDATE deletion_requests SET status = 'in_progress' WHERE request_id = ?
|
|
702
|
+
`).bind(requestId).run();
|
|
703
|
+
|
|
704
|
+
// Perform deletion
|
|
705
|
+
const deletionResult = await this.deleteUserData(request.user_id);
|
|
706
|
+
|
|
707
|
+
// Update status to completed
|
|
708
|
+
await this.db.prepare(`
|
|
709
|
+
UPDATE deletion_requests
|
|
710
|
+
SET status = 'completed', completed_at = ?, processed_by = 'system',
|
|
711
|
+
processing_notes = ?
|
|
712
|
+
WHERE request_id = ?
|
|
713
|
+
`).bind(
|
|
714
|
+
new Date().toISOString(),
|
|
715
|
+
JSON.stringify(deletionResult),
|
|
716
|
+
requestId
|
|
717
|
+
).run();
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
request_id: requestId,
|
|
721
|
+
user_id: request.user_id,
|
|
722
|
+
deleted_at: new Date().toISOString(),
|
|
723
|
+
deletion_summary: deletionResult
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Submit data portability request (GDPR Art. 20, LGPD Art. 18)
|
|
729
|
+
* @param {string} userId - User ID
|
|
730
|
+
* @param {object} metadata - Request metadata
|
|
731
|
+
* @returns {Promise<string>} Request ID
|
|
732
|
+
*/
|
|
733
|
+
async submitPortabilityRequest(userId, metadata = {}) {
|
|
734
|
+
const requestId = this.generateRequestId('portability');
|
|
735
|
+
|
|
736
|
+
await this.db.prepare(`
|
|
737
|
+
INSERT INTO deletion_requests
|
|
738
|
+
(user_id, request_id, request_type, status, requested_at, ip_address, user_agent)
|
|
739
|
+
VALUES (?, ?, 'portability', 'pending', ?, ?, ?)
|
|
740
|
+
`).bind(
|
|
741
|
+
userId,
|
|
742
|
+
requestId,
|
|
743
|
+
new Date().toISOString(),
|
|
744
|
+
metadata.ip_address || null,
|
|
745
|
+
metadata.user_agent || null
|
|
746
|
+
).run();
|
|
747
|
+
|
|
748
|
+
return requestId;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Process data portability request
|
|
753
|
+
* @param {string} requestId - Request ID
|
|
754
|
+
* @param {string} format - Export format (json, csv)
|
|
755
|
+
* @returns {Promise<object>} Portable data package
|
|
756
|
+
*/
|
|
757
|
+
async processPortabilityRequest(requestId, format = 'json') {
|
|
758
|
+
const request = await this.db.prepare(`
|
|
759
|
+
SELECT * FROM deletion_requests WHERE request_id = ?
|
|
760
|
+
`).bind(requestId).first();
|
|
761
|
+
|
|
762
|
+
if (!request) {
|
|
763
|
+
throw new Error('Request not found');
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (request.status !== 'pending') {
|
|
767
|
+
throw new Error('Request already processed');
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Update status to in_progress
|
|
771
|
+
await this.db.prepare(`
|
|
772
|
+
UPDATE deletion_requests SET status = 'in_progress' WHERE request_id = ?
|
|
773
|
+
`).bind(requestId).run();
|
|
774
|
+
|
|
775
|
+
// Collect and format user data
|
|
776
|
+
const userData = await this.collectUserData(request.user_id);
|
|
777
|
+
const portableData = await this.formatPortableData(userData, format);
|
|
778
|
+
|
|
779
|
+
// Update status to completed
|
|
780
|
+
await this.db.prepare(`
|
|
781
|
+
UPDATE deletion_requests
|
|
782
|
+
SET status = 'completed', completed_at = ?, processed_by = 'system'
|
|
783
|
+
WHERE request_id = ?
|
|
784
|
+
`).bind(new Date().toISOString(), requestId).run();
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
request_id: requestId,
|
|
788
|
+
user_id: request.user_id,
|
|
789
|
+
exported_at: new Date().toISOString(),
|
|
790
|
+
format: format,
|
|
791
|
+
data: portableData
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Submit data rectification request (GDPR Art. 16, LGPD Art. 18)
|
|
797
|
+
* @param {string} userId - User ID
|
|
798
|
+
* @param {object} corrections - Data corrections
|
|
799
|
+
* @param {object} metadata - Request metadata
|
|
800
|
+
* @returns {Promise<string>} Request ID
|
|
801
|
+
*/
|
|
802
|
+
async submitRectificationRequest(userId, corrections, metadata = {}) {
|
|
803
|
+
const requestId = this.generateRequestId('rectification');
|
|
804
|
+
|
|
805
|
+
await this.db.prepare(`
|
|
806
|
+
INSERT INTO deletion_requests
|
|
807
|
+
(user_id, request_id, request_type, status, requested_at,
|
|
808
|
+
ip_address, user_agent, processing_notes)
|
|
809
|
+
VALUES (?, ?, 'rectification', 'pending', ?, ?, ?)
|
|
810
|
+
`).bind(
|
|
811
|
+
userId,
|
|
812
|
+
requestId,
|
|
813
|
+
new Date().toISOString(),
|
|
814
|
+
metadata.ip_address || null,
|
|
815
|
+
metadata.user_agent || null,
|
|
816
|
+
JSON.stringify(corrections)
|
|
817
|
+
).run();
|
|
818
|
+
|
|
819
|
+
return requestId;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Process data rectification request
|
|
824
|
+
* @param {string} requestId - Request ID
|
|
825
|
+
* @returns {Promise<object>} Rectification result
|
|
826
|
+
*/
|
|
827
|
+
async processRectificationRequest(requestId) {
|
|
828
|
+
const request = await this.db.prepare(`
|
|
829
|
+
SELECT * FROM deletion_requests WHERE request_id = ?
|
|
830
|
+
`).bind(requestId).first();
|
|
831
|
+
|
|
832
|
+
if (!request) {
|
|
833
|
+
throw new Error('Request not found');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (request.status !== 'pending') {
|
|
837
|
+
throw new Error('Request already processed');
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const corrections = JSON.parse(request.processing_notes || '{}');
|
|
841
|
+
|
|
842
|
+
// Update status to in_progress
|
|
843
|
+
await this.db.prepare(`
|
|
844
|
+
UPDATE deletion_requests SET status = 'in_progress' WHERE request_id = ?
|
|
845
|
+
`).bind(requestId).run();
|
|
846
|
+
|
|
847
|
+
// Apply corrections
|
|
848
|
+
const rectificationResult = await this.applyDataCorrections(request.user_id, corrections);
|
|
849
|
+
|
|
850
|
+
// Update status to completed
|
|
851
|
+
await this.db.prepare(`
|
|
852
|
+
UPDATE deletion_requests
|
|
853
|
+
SET status = 'completed', completed_at = ?, processed_by = 'system',
|
|
854
|
+
processing_notes = ?
|
|
855
|
+
WHERE request_id = ?
|
|
856
|
+
`).bind(
|
|
857
|
+
new Date().toISOString(),
|
|
858
|
+
JSON.stringify(rectificationResult),
|
|
859
|
+
requestId
|
|
860
|
+
).run();
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
request_id: requestId,
|
|
864
|
+
user_id: request.user_id,
|
|
865
|
+
corrected_at: new Date().toISOString(),
|
|
866
|
+
corrections_applied: rectificationResult
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Collect all user data from the system
|
|
872
|
+
* @param {string} userId - User ID
|
|
873
|
+
* @returns {Promise<object>} All user data
|
|
874
|
+
*/
|
|
875
|
+
async collectUserData(userId) {
|
|
876
|
+
return {
|
|
877
|
+
user_profile: await this.getUserProfile(userId),
|
|
878
|
+
consent_records: await this.getUserConsents(userId),
|
|
879
|
+
cookie_preferences: await this.getUserCookiePreferences(userId),
|
|
880
|
+
user_journeys: await this.getUserJourneys(userId),
|
|
881
|
+
attribution_data: await this.getUserAttributionData(userId),
|
|
882
|
+
security_data: await this.getUserSecurityData(userId)
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Get user profile data
|
|
888
|
+
* @param {string} userId - User ID
|
|
889
|
+
* @returns {Promise<Array>} User profile data
|
|
890
|
+
*/
|
|
891
|
+
async getUserProfile(userId) {
|
|
892
|
+
const results = await this.db.prepare(`
|
|
893
|
+
SELECT * FROM user_profiles WHERE user_id = ?
|
|
894
|
+
`).bind(userId).all();
|
|
895
|
+
|
|
896
|
+
return results;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Get user consent records
|
|
901
|
+
* @param {string} userId - User ID
|
|
902
|
+
* @returns {Promise<Array>} Consent records
|
|
903
|
+
*/
|
|
904
|
+
async getUserConsents(userId) {
|
|
905
|
+
const results = await this.db.prepare(`
|
|
906
|
+
SELECT * FROM consent_records WHERE user_id = ?
|
|
907
|
+
`).bind(userId).all();
|
|
908
|
+
|
|
909
|
+
return results;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Get user cookie preferences
|
|
914
|
+
* @param {string} userId - User ID
|
|
915
|
+
* @returns {Promise<object>} Cookie preferences
|
|
916
|
+
*/
|
|
917
|
+
async getUserCookiePreferences(userId) {
|
|
918
|
+
const result = await this.db.prepare(`
|
|
919
|
+
SELECT * FROM cookie_preferences WHERE user_id = ?
|
|
920
|
+
`).bind(userId).first();
|
|
921
|
+
|
|
922
|
+
return result || {};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Get user journey data
|
|
927
|
+
* @param {string} userId - User ID
|
|
928
|
+
* @returns {Promise<Array>} User journeys
|
|
929
|
+
*/
|
|
930
|
+
async getUserJourneys(userId) {
|
|
931
|
+
const results = await this.db.prepare(`
|
|
932
|
+
SELECT * FROM user_journeys WHERE user_id = ?
|
|
933
|
+
`).bind(userId).all();
|
|
934
|
+
|
|
935
|
+
return results;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Get user attribution data
|
|
940
|
+
* @param {string} userId - User ID
|
|
941
|
+
* @returns {Promise<Array>} Attribution data
|
|
942
|
+
*/
|
|
943
|
+
async getUserAttributionData(userId) {
|
|
944
|
+
const results = await this.db.prepare(`
|
|
945
|
+
SELECT a.* FROM multi_touch_attribution a
|
|
946
|
+
INNER JOIN user_journeys j ON a.conversion_id = j.conversion_id
|
|
947
|
+
WHERE j.user_id = ?
|
|
948
|
+
`).bind(userId).all();
|
|
949
|
+
|
|
950
|
+
return results;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Get user security data (excluding sensitive data)
|
|
955
|
+
* @param {string} userId - User ID
|
|
956
|
+
* @returns {Promise<Array>} Security data
|
|
957
|
+
*/
|
|
958
|
+
async getUserSecurityData(userId) {
|
|
959
|
+
// Return only non-sensitive security data
|
|
960
|
+
const results = await this.db.prepare(`
|
|
961
|
+
SELECT id, timestamp, log_type, action, blocked
|
|
962
|
+
FROM audit_logs
|
|
963
|
+
WHERE user_id = ?
|
|
964
|
+
`).bind(userId).all();
|
|
965
|
+
|
|
966
|
+
return results;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Delete all user data from the system
|
|
971
|
+
* @param {string} userId - User ID
|
|
972
|
+
* @returns {Promise<object>} Deletion summary
|
|
973
|
+
*/
|
|
974
|
+
async deleteUserData(userId) {
|
|
975
|
+
const deletionSummary = {
|
|
976
|
+
tables: {},
|
|
977
|
+
deleted_at: new Date().toISOString()
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// Delete from consent_records
|
|
981
|
+
const consentResult = await this.db.prepare(`
|
|
982
|
+
DELETE FROM consent_records WHERE user_id = ?
|
|
983
|
+
`).bind(userId).run();
|
|
984
|
+
deletionSummary.tables.consent_records = consentResult.meta.changes;
|
|
985
|
+
|
|
986
|
+
// Delete from cookie_preferences
|
|
987
|
+
const cookieResult = await this.db.prepare(`
|
|
988
|
+
DELETE FROM cookie_preferences WHERE user_id = ?
|
|
989
|
+
`).bind(userId).run();
|
|
990
|
+
deletionSummary.tables.cookie_preferences = cookieResult.meta.changes;
|
|
991
|
+
|
|
992
|
+
// Delete from user_journeys
|
|
993
|
+
const journeyResult = await this.db.prepare(`
|
|
994
|
+
DELETE FROM user_journeys WHERE user_id = ?
|
|
995
|
+
`).bind(userId).run();
|
|
996
|
+
deletionSummary.tables.user_journeys = journeyResult.meta.changes;
|
|
997
|
+
|
|
998
|
+
// Delete from multi_touch_attribution (via user_journeys conversion_id)
|
|
999
|
+
// This is handled by cascade delete in user_journeys
|
|
1000
|
+
|
|
1001
|
+
// Delete from user_profiles
|
|
1002
|
+
const profileResult = await this.db.prepare(`
|
|
1003
|
+
DELETE FROM user_profiles WHERE user_id = ?
|
|
1004
|
+
`).bind(userId).run();
|
|
1005
|
+
deletionSummary.tables.user_profiles = profileResult.meta.changes;
|
|
1006
|
+
|
|
1007
|
+
// Delete from ip_violations (if any)
|
|
1008
|
+
const violationsResult = await this.db.prepare(`
|
|
1009
|
+
DELETE FROM ip_violations WHERE user_id = ?
|
|
1010
|
+
`).bind(userId).run();
|
|
1011
|
+
deletionSummary.tables.ip_violations = violationsResult.meta.changes;
|
|
1012
|
+
|
|
1013
|
+
return deletionSummary;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Apply data corrections
|
|
1018
|
+
* @param {string} userId - User ID
|
|
1019
|
+
* @param {object} corrections - Data corrections
|
|
1020
|
+
* @returns {Promise<object>} Rectification summary
|
|
1021
|
+
*/
|
|
1022
|
+
async applyDataCorrections(userId, corrections) {
|
|
1023
|
+
const rectificationSummary = {
|
|
1024
|
+
corrections: [],
|
|
1025
|
+
corrected_at: new Date().toISOString()
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// Apply corrections to user_profiles
|
|
1029
|
+
if (corrections.user_profile) {
|
|
1030
|
+
const profileUpdates = corrections.user_profile;
|
|
1031
|
+
const setClauses = Object.keys(profileUpdates).map(key => `${key} = ?`).join(', ');
|
|
1032
|
+
const values = Object.values(profileUpdates);
|
|
1033
|
+
|
|
1034
|
+
await this.db.prepare(`
|
|
1035
|
+
UPDATE user_profiles
|
|
1036
|
+
SET ${setClauses}
|
|
1037
|
+
WHERE user_id = ?
|
|
1038
|
+
`).bind(...values, userId).run();
|
|
1039
|
+
|
|
1040
|
+
rectificationSummary.corrections.push({
|
|
1041
|
+
table: 'user_profiles',
|
|
1042
|
+
fields: Object.keys(profileUpdates)
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return rectificationSummary;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Format portable data in specified format
|
|
1051
|
+
* @param {object} userData - User data
|
|
1052
|
+
* @param {string} format - Export format
|
|
1053
|
+
* @returns {Promise<string>} Formatted data
|
|
1054
|
+
*/
|
|
1055
|
+
async formatPortableData(userData, format) {
|
|
1056
|
+
if (format === 'json') {
|
|
1057
|
+
return JSON.stringify(userData, null, 2);
|
|
1058
|
+
} else if (format === 'csv') {
|
|
1059
|
+
return await this.convertToCSV(userData);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return JSON.stringify(userData, null, 2);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Convert user data to CSV format
|
|
1067
|
+
* @param {object} userData - User data
|
|
1068
|
+
* @returns {Promise<string>} CSV data
|
|
1069
|
+
*/
|
|
1070
|
+
async convertToCSV(userData) {
|
|
1071
|
+
// Flatten data and convert to CSV
|
|
1072
|
+
// This is a simplified implementation
|
|
1073
|
+
const rows = [];
|
|
1074
|
+
|
|
1075
|
+
for (const [table, data] of Object.entries(userData)) {
|
|
1076
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
1077
|
+
const headers = Object.keys(data[0]).join(',');
|
|
1078
|
+
const values = data.map(row =>
|
|
1079
|
+
Object.values(row).map(val =>
|
|
1080
|
+
typeof val === 'string' ? `"${val}"` : val
|
|
1081
|
+
).join(',')
|
|
1082
|
+
).join('\n');
|
|
1083
|
+
|
|
1084
|
+
rows.push(`\n### ${table} ###\n`);
|
|
1085
|
+
rows.push(headers);
|
|
1086
|
+
rows.push(values);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
return rows.join('\n');
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Generate unique request ID
|
|
1095
|
+
* @param {string} type - Request type
|
|
1096
|
+
* @returns {string} Request ID
|
|
1097
|
+
*/
|
|
1098
|
+
generateRequestId(type) {
|
|
1099
|
+
return `${type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Global data rights manager instance
|
|
1104
|
+
let dataRightsManager = null;
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
---
|
|
1108
|
+
|
|
1109
|
+
## ⏱️ Data Retention Management
|
|
1110
|
+
|
|
1111
|
+
### Automated Data Retention Policies
|
|
1112
|
+
|
|
1113
|
+
```javascript
|
|
1114
|
+
/**
|
|
1115
|
+
* Data Retention Manager
|
|
1116
|
+
* Manages automated data retention and deletion policies
|
|
1117
|
+
*/
|
|
1118
|
+
class DataRetentionManager {
|
|
1119
|
+
constructor(env) {
|
|
1120
|
+
this.db = env.DB;
|
|
1121
|
+
this.defaultRetentionPolicies = [
|
|
1122
|
+
{
|
|
1123
|
+
data_type: 'user_journeys',
|
|
1124
|
+
retention_period_days: 365 * 2, // 2 years
|
|
1125
|
+
legal_basis: 'legitimate_interest',
|
|
1126
|
+
policy_name: 'User Journey Retention',
|
|
1127
|
+
description: 'Retain user journey data for 2 years for attribution and analytics'
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
data_type: 'consent_records',
|
|
1131
|
+
retention_period_days: 365 * 3, // 3 years
|
|
1132
|
+
legal_basis: 'legal_obligation',
|
|
1133
|
+
policy_name: 'Consent Record Retention',
|
|
1134
|
+
description: 'Retain consent records for 3 years for compliance audit'
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
data_type: 'audit_logs',
|
|
1138
|
+
retention_period_days: 365 * 5, // 5 years
|
|
1139
|
+
legal_basis: 'legal_obligation',
|
|
1140
|
+
policy_name: 'Audit Log Retention',
|
|
1141
|
+
description: 'Retain audit logs for 5 years for security compliance'
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
data_type: 'cookie_preferences',
|
|
1145
|
+
retention_period_days: 365 * 2, // 2 years
|
|
1146
|
+
legal_basis: 'consent',
|
|
1147
|
+
policy_name: 'Cookie Preferences Retention',
|
|
1148
|
+
description: 'Retain cookie preferences for 2 years'
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
data_type: 'ip_violations',
|
|
1152
|
+
retention_period_days: 90, // 90 days
|
|
1153
|
+
legal_basis: 'legitimate_interest',
|
|
1154
|
+
policy_name: 'IP Violations Retention',
|
|
1155
|
+
description: 'Retain IP violation records for 90 days for security purposes'
|
|
1156
|
+
}
|
|
1157
|
+
];
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Initialize default retention policies
|
|
1162
|
+
*/
|
|
1163
|
+
async initializePolicies() {
|
|
1164
|
+
for (const policy of this.defaultRetentionPolicies) {
|
|
1165
|
+
await this.db.prepare(`
|
|
1166
|
+
INSERT OR IGNORE INTO retention_policies
|
|
1167
|
+
(data_type, retention_period_days, legal_basis, policy_name, description)
|
|
1168
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1169
|
+
`).bind(
|
|
1170
|
+
policy.data_type,
|
|
1171
|
+
policy.retention_period_days,
|
|
1172
|
+
policy.legal_basis,
|
|
1173
|
+
policy.policy_name,
|
|
1174
|
+
policy.description
|
|
1175
|
+
).run();
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Execute data retention cleanup (run daily)
|
|
1181
|
+
* @returns {Promise<object>} Cleanup summary
|
|
1182
|
+
*/
|
|
1183
|
+
async executeRetentionCleanup() {
|
|
1184
|
+
const cleanupSummary = {
|
|
1185
|
+
timestamp: new Date().toISOString(),
|
|
1186
|
+
policies_executed: [],
|
|
1187
|
+
total_records_deleted: 0,
|
|
1188
|
+
tables_cleaned: []
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
// Get all active retention policies
|
|
1192
|
+
const policies = await this.db.prepare(`
|
|
1193
|
+
SELECT * FROM retention_policies
|
|
1194
|
+
`).all();
|
|
1195
|
+
|
|
1196
|
+
for (const policy of policies) {
|
|
1197
|
+
const deletionResult = await this.executePolicy(policy);
|
|
1198
|
+
cleanupSummary.policies_executed.push({
|
|
1199
|
+
policy_name: policy.policy_name,
|
|
1200
|
+
data_type: policy.data_type,
|
|
1201
|
+
records_deleted: deletionResult.deleted_count
|
|
1202
|
+
});
|
|
1203
|
+
cleanupSummary.total_records_deleted += deletionResult.deleted_count;
|
|
1204
|
+
|
|
1205
|
+
if (!cleanupSummary.tables_cleaned.includes(policy.data_type)) {
|
|
1206
|
+
cleanupSummary.tables_cleaned.push(policy.data_type);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
return cleanupSummary;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Execute a single retention policy
|
|
1215
|
+
* @param {object} policy - Retention policy
|
|
1216
|
+
* @returns {Promise<object>} Deletion result
|
|
1217
|
+
*/
|
|
1218
|
+
async executePolicy(policy) {
|
|
1219
|
+
const cutoffDate = new Date();
|
|
1220
|
+
cutoffDate.setDate(cutoffDate.getDate() - policy.retention_period_days);
|
|
1221
|
+
const cutoffISO = cutoffDate.toISOString();
|
|
1222
|
+
|
|
1223
|
+
let deletedCount = 0;
|
|
1224
|
+
|
|
1225
|
+
switch (policy.data_type) {
|
|
1226
|
+
case 'user_journeys':
|
|
1227
|
+
deletedCount = await this.deleteOldUserJourneys(cutoffISO);
|
|
1228
|
+
break;
|
|
1229
|
+
|
|
1230
|
+
case 'consent_records':
|
|
1231
|
+
deletedCount = await this.deleteOldConsentRecords(cutoffISO);
|
|
1232
|
+
break;
|
|
1233
|
+
|
|
1234
|
+
case 'audit_logs':
|
|
1235
|
+
deletedCount = await this.deleteOldAuditLogs(cutoffISO);
|
|
1236
|
+
break;
|
|
1237
|
+
|
|
1238
|
+
case 'cookie_preferences':
|
|
1239
|
+
deletedCount = await this.deleteOldCookiePreferences(cutoffISO);
|
|
1240
|
+
break;
|
|
1241
|
+
|
|
1242
|
+
case 'ip_violations':
|
|
1243
|
+
deletedCount = await this.deleteOldIPViolations(cutoffISO);
|
|
1244
|
+
break;
|
|
1245
|
+
|
|
1246
|
+
default:
|
|
1247
|
+
console.warn(`Unknown data type in retention policy: ${policy.data_type}`);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
return {
|
|
1251
|
+
data_type: policy.data_type,
|
|
1252
|
+
cutoff_date: cutoffISO,
|
|
1253
|
+
deleted_count: deletedCount
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Delete old user journeys
|
|
1259
|
+
* @param {string} cutoffDate - Cutoff date ISO string
|
|
1260
|
+
* @returns {Promise<number>} Number of deleted records
|
|
1261
|
+
*/
|
|
1262
|
+
async deleteOldUserJourneys(cutoffDate) {
|
|
1263
|
+
const result = await this.db.prepare(`
|
|
1264
|
+
DELETE FROM user_journeys
|
|
1265
|
+
WHERE created_at < ? AND archived = 0
|
|
1266
|
+
`).bind(cutoffDate).run();
|
|
1267
|
+
|
|
1268
|
+
return result.meta.changes || 0;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Delete old consent records (keep only consent history)
|
|
1273
|
+
* @param {string} cutoffDate - Cutoff date ISO string
|
|
1274
|
+
* @returns {Promise<number>} Number of deleted records
|
|
1275
|
+
*/
|
|
1276
|
+
async deleteOldConsentRecords(cutoffDate) {
|
|
1277
|
+
// Only delete old consent_records, keep consent_history
|
|
1278
|
+
const result = await this.db.prepare(`
|
|
1279
|
+
DELETE FROM consent_records
|
|
1280
|
+
WHERE timestamp < ? AND consent_given = false
|
|
1281
|
+
`).bind(cutoffDate).run();
|
|
1282
|
+
|
|
1283
|
+
return result.meta.changes || 0;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Delete old audit logs
|
|
1288
|
+
* @param {string} cutoffDate - Cutoff date ISO string
|
|
1289
|
+
* @returns {Promise<number>} Number of deleted records
|
|
1290
|
+
*/
|
|
1291
|
+
async deleteOldAuditLogs(cutoffDate) {
|
|
1292
|
+
const result = await this.db.prepare(`
|
|
1293
|
+
DELETE FROM audit_logs
|
|
1294
|
+
WHERE timestamp < ? AND severity != 'CRITICAL'
|
|
1295
|
+
`).bind(cutoffDate).run();
|
|
1296
|
+
|
|
1297
|
+
return result.meta.changes || 0;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Delete old cookie preferences
|
|
1302
|
+
* @param {string} cutoffDate - Cutoff date ISO string
|
|
1303
|
+
* @returns {Promise<number>} Number of deleted records
|
|
1304
|
+
*/
|
|
1305
|
+
async deleteOldCookiePreferences(cutoffDate) {
|
|
1306
|
+
const result = await this.db.prepare(`
|
|
1307
|
+
DELETE FROM cookie_preferences
|
|
1308
|
+
WHERE preferences_updated < ?
|
|
1309
|
+
`).bind(cutoffDate).run();
|
|
1310
|
+
|
|
1311
|
+
return result.meta.changes || 0;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Delete old IP violation records
|
|
1316
|
+
* @param {string} cutoffDate - Cutoff date ISO string
|
|
1317
|
+
* @returns {Promise<number>} Number of deleted records
|
|
1318
|
+
*/
|
|
1319
|
+
async deleteOldIPViolations(cutoffDate) {
|
|
1320
|
+
const result = await this.db.prepare(`
|
|
1321
|
+
DELETE FROM ip_violations
|
|
1322
|
+
WHERE timestamp < ?
|
|
1323
|
+
`).bind(cutoffDate).run();
|
|
1324
|
+
|
|
1325
|
+
return result.meta.changes || 0;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* Get retention policy statistics
|
|
1330
|
+
* @returns {Promise<object>} Retention statistics
|
|
1331
|
+
*/
|
|
1332
|
+
async getRetentionStats() {
|
|
1333
|
+
const policies = await this.db.prepare(`
|
|
1334
|
+
SELECT * FROM retention_policies
|
|
1335
|
+
`).all();
|
|
1336
|
+
|
|
1337
|
+
const stats = {
|
|
1338
|
+
policies: policies.length,
|
|
1339
|
+
data_types: {},
|
|
1340
|
+
total_retention_days: 0
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
for (const policy of policies) {
|
|
1344
|
+
stats.data_types[policy.data_type] = policy.retention_period_days;
|
|
1345
|
+
stats.total_retention_days += policy.retention_period_days;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Count records in each table
|
|
1349
|
+
stats.record_counts = {
|
|
1350
|
+
user_journeys: await this.countRecords('user_journeys'),
|
|
1351
|
+
consent_records: await this.countRecords('consent_records'),
|
|
1352
|
+
audit_logs: await this.countRecords('audit_logs'),
|
|
1353
|
+
cookie_preferences: await this.countRecords('cookie_preferences'),
|
|
1354
|
+
ip_violations: await this.countRecords('ip_violations')
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
return stats;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Count records in a table
|
|
1362
|
+
* @param {string} tableName - Table name
|
|
1363
|
+
* @returns {Promise<number>} Record count
|
|
1364
|
+
*/
|
|
1365
|
+
async countRecords(tableName) {
|
|
1366
|
+
const result = await this.db.prepare(`
|
|
1367
|
+
SELECT COUNT(*) as count FROM ${tableName}
|
|
1368
|
+
`).first();
|
|
1369
|
+
|
|
1370
|
+
return result ? result.count : 0;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Global data retention manager instance
|
|
1375
|
+
let dataRetentionManager = null;
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
---
|
|
1379
|
+
|
|
1380
|
+
## 🎯 Compliance Audit Trail
|
|
1381
|
+
|
|
1382
|
+
### Comprehensive Audit Logging
|
|
1383
|
+
|
|
1384
|
+
```javascript
|
|
1385
|
+
/**
|
|
1386
|
+
* Compliance Audit Logger
|
|
1387
|
+
* Records all compliance-related activities for audit purposes
|
|
1388
|
+
*/
|
|
1389
|
+
class ComplianceAuditLogger {
|
|
1390
|
+
constructor(env) {
|
|
1391
|
+
this.db = env.DB;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Log compliance activity
|
|
1396
|
+
* @param {string} userId - User ID (optional)
|
|
1397
|
+
* @param {string} action - Action performed
|
|
1398
|
+
* @param {string} category - Category (consent, data_rights, retention, etc.)
|
|
1399
|
+
* @param {object} details - Action details
|
|
1400
|
+
* @param {object} metadata - Metadata (IP, user-agent)
|
|
1401
|
+
*/
|
|
1402
|
+
async logActivity(userId, action, category, details, metadata = {}) {
|
|
1403
|
+
await this.db.prepare(`
|
|
1404
|
+
INSERT INTO audit_logs
|
|
1405
|
+
(timestamp, user_id, log_type, action, details, ip_address, user_agent)
|
|
1406
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1407
|
+
`).bind(
|
|
1408
|
+
new Date().toISOString(),
|
|
1409
|
+
userId || null,
|
|
1410
|
+
category,
|
|
1411
|
+
action,
|
|
1412
|
+
JSON.stringify(details),
|
|
1413
|
+
metadata.ip_address || null,
|
|
1414
|
+
metadata.user_agent || null
|
|
1415
|
+
).run();
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Log consent activity
|
|
1420
|
+
* @param {string} userId - User ID
|
|
1421
|
+
* @param {string} action - Action (granted, revoked, updated)
|
|
1422
|
+
* @param {string} consentType - Consent type
|
|
1423
|
+
* @param {object} details - Additional details
|
|
1424
|
+
* @param {object} metadata - Metadata
|
|
1425
|
+
*/
|
|
1426
|
+
async logConsentActivity(userId, action, consentType, details, metadata) {
|
|
1427
|
+
await this.logActivity(
|
|
1428
|
+
userId,
|
|
1429
|
+
`consent_${action}`,
|
|
1430
|
+
'consent',
|
|
1431
|
+
{
|
|
1432
|
+
consent_type: consentType,
|
|
1433
|
+
...details
|
|
1434
|
+
},
|
|
1435
|
+
metadata
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Log data rights activity
|
|
1441
|
+
* @param {string} userId - User ID
|
|
1442
|
+
* @param {string} action - Action (access, deletion, portability, rectification)
|
|
1443
|
+
* @param {string} requestId - Request ID
|
|
1444
|
+
* @param {object} details - Additional details
|
|
1445
|
+
* @param {object} metadata - Metadata
|
|
1446
|
+
*/
|
|
1447
|
+
async logDataRightsActivity(userId, action, requestId, details, metadata) {
|
|
1448
|
+
await this.logActivity(
|
|
1449
|
+
userId,
|
|
1450
|
+
`data_rights_${action}`,
|
|
1451
|
+
'data_rights',
|
|
1452
|
+
{
|
|
1453
|
+
request_id: requestId,
|
|
1454
|
+
action_type: action,
|
|
1455
|
+
...details
|
|
1456
|
+
},
|
|
1457
|
+
metadata
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* Log retention activity
|
|
1463
|
+
* @param {string} action - Action (cleanup, policy_update)
|
|
1464
|
+
* @param {object} details - Policy details
|
|
1465
|
+
*/
|
|
1466
|
+
async logRetentionActivity(action, details) {
|
|
1467
|
+
await this.logActivity(
|
|
1468
|
+
null,
|
|
1469
|
+
`retention_${action}`,
|
|
1470
|
+
'retention',
|
|
1471
|
+
details,
|
|
1472
|
+
{}
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* Get audit logs for a user
|
|
1478
|
+
* @param {string} userId - User ID
|
|
1479
|
+
* @param {number} limit - Limit results
|
|
1480
|
+
* @returns {Promise<Array>} Audit logs
|
|
1481
|
+
*/
|
|
1482
|
+
async getUserAuditLogs(userId, limit = 100) {
|
|
1483
|
+
const results = await this.db.prepare(`
|
|
1484
|
+
SELECT * FROM audit_logs
|
|
1485
|
+
WHERE user_id = ?
|
|
1486
|
+
ORDER BY timestamp DESC
|
|
1487
|
+
LIMIT ?
|
|
1488
|
+
`).bind(userId, limit).all();
|
|
1489
|
+
|
|
1490
|
+
return results;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Get audit logs by category
|
|
1495
|
+
* @param {string} category - Log category
|
|
1496
|
+
* @param {number} limit - Limit results
|
|
1497
|
+
* @returns {Promise<Array>} Audit logs
|
|
1498
|
+
*/
|
|
1499
|
+
async getAuditLogsByCategory(category, limit = 100) {
|
|
1500
|
+
const results = await this.db.prepare(`
|
|
1501
|
+
SELECT * FROM audit_logs
|
|
1502
|
+
WHERE log_type = ?
|
|
1503
|
+
ORDER BY timestamp DESC
|
|
1504
|
+
LIMIT ?
|
|
1505
|
+
`).bind(category, limit).all();
|
|
1506
|
+
|
|
1507
|
+
return results;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* Get recent audit logs
|
|
1512
|
+
* @param {number} limit - Limit results
|
|
1513
|
+
* @returns {Promise<Array>} Recent audit logs
|
|
1514
|
+
*/
|
|
1515
|
+
async getRecentAuditLogs(limit = 50) {
|
|
1516
|
+
const results = await this.db.prepare(`
|
|
1517
|
+
SELECT * FROM audit_logs
|
|
1518
|
+
ORDER BY timestamp DESC
|
|
1519
|
+
LIMIT ?
|
|
1520
|
+
`).bind(limit).all();
|
|
1521
|
+
|
|
1522
|
+
return results;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Global compliance audit logger instance
|
|
1527
|
+
let complianceAuditLogger = null;
|
|
1528
|
+
```
|
|
1529
|
+
|
|
1530
|
+
---
|
|
1531
|
+
|
|
1532
|
+
## 🔗 Worker API Endpoints
|
|
1533
|
+
|
|
1534
|
+
### Compliance Management Endpoints
|
|
1535
|
+
|
|
1536
|
+
```javascript
|
|
1537
|
+
/**
|
|
1538
|
+
* Compliance Management Endpoints
|
|
1539
|
+
*/
|
|
1540
|
+
export default {
|
|
1541
|
+
async fetch(request, env, ctx) {
|
|
1542
|
+
// Initialize components
|
|
1543
|
+
if (!consentManager) consentManager = new ConsentManager(env);
|
|
1544
|
+
if (!dataRightsManager) dataRightsManager = new DataRightsManager(env);
|
|
1545
|
+
if (!dataRetentionManager) {
|
|
1546
|
+
dataRetentionManager = new DataRetentionManager(env);
|
|
1547
|
+
await dataRetentionManager.initializePolicies();
|
|
1548
|
+
}
|
|
1549
|
+
if (!complianceAuditLogger) complianceAuditLogger = new ComplianceAuditLogger(env);
|
|
1550
|
+
|
|
1551
|
+
const url = new URL(request.url);
|
|
1552
|
+
const path = url.pathname;
|
|
1553
|
+
const method = request.method;
|
|
1554
|
+
|
|
1555
|
+
// Consent management endpoints
|
|
1556
|
+
if (path === '/api/compliance/consent/grant' && method === 'POST') {
|
|
1557
|
+
return handleGrantConsent(request, env);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
if (path === '/api/compliance/consent/revoke' && method === 'POST') {
|
|
1561
|
+
return handleRevokeConsent(request, env);
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
if (path === '/api/compliance/consent/check' && method === 'GET') {
|
|
1565
|
+
return handleCheckConsent(request, env);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (path === '/api/compliance/consent/preferences' && method === 'GET') {
|
|
1569
|
+
return handleGetCookiePreferences(request, env);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
if (path === '/api/compliance/consent/preferences' && method === 'POST') {
|
|
1573
|
+
return handleUpdateCookiePreferences(request, env);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Data rights endpoints
|
|
1577
|
+
if (path === '/api/compliance/data-rights/access' && method === 'POST') {
|
|
1578
|
+
return handleSubmitAccessRequest(request, env);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
if (path === '/api/compliance/data-rights/deletion' && method === 'POST') {
|
|
1582
|
+
return handleSubmitDeletionRequest(request, env);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
if (path === '/api/compliance/data-rights/portability' && method === 'POST') {
|
|
1586
|
+
return handleSubmitPortabilityRequest(request, env);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if (path === '/api/compliance/data-rights/rectification' && method === 'POST') {
|
|
1590
|
+
return handleSubmitRectificationRequest(request, env);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
if (path === '/api/compliance/data-rights/request' && method === 'GET') {
|
|
1594
|
+
return handleGetDataRightsRequest(request, env);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (path === '/api/compliance/data-rights/process' && method === 'POST') {
|
|
1598
|
+
return handleProcessDataRightsRequest(request, env);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Retention management endpoints
|
|
1602
|
+
if (path === '/api/compliance/retention/cleanup' && method === 'POST') {
|
|
1603
|
+
return handleRetentionCleanup(request, env);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
if (path === '/api/compliance/retention/stats' && method === 'GET') {
|
|
1607
|
+
return handleRetentionStats(request, env);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Audit endpoints
|
|
1611
|
+
if (path === '/api/compliance/audit/user' && method === 'GET') {
|
|
1612
|
+
return handleGetUserAuditLogs(request, env);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (path === '/api/compliance/audit/recent' && method === 'GET') {
|
|
1616
|
+
return handleGetRecentAuditLogs(request, env);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Policy endpoints
|
|
1620
|
+
if (path === '/api/compliance/policy/current' && method === 'GET') {
|
|
1621
|
+
return handleGetCurrentPolicy(request, env);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
return new Response('Not Found', { status: 404 });
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Handle /api/compliance/consent/grant
|
|
1630
|
+
*/
|
|
1631
|
+
async function handleGrantConsent(request, env) {
|
|
1632
|
+
try {
|
|
1633
|
+
const body = await request.json();
|
|
1634
|
+
const { user_id, consent_type } = body;
|
|
1635
|
+
|
|
1636
|
+
if (!user_id || !consent_type) {
|
|
1637
|
+
return new Response(JSON.stringify({
|
|
1638
|
+
success: false,
|
|
1639
|
+
error: 'Missing required fields: user_id, consent_type'
|
|
1640
|
+
}), {
|
|
1641
|
+
status: 400,
|
|
1642
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
const metadata = {
|
|
1647
|
+
ip_address: request.headers.get('CF-Connecting-IP'),
|
|
1648
|
+
user_agent: request.headers.get('User-Agent'),
|
|
1649
|
+
source: body.source || 'api'
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
const consent = await consentManager.grantConsent(user_id, consent_type, metadata);
|
|
1653
|
+
|
|
1654
|
+
await complianceAuditLogger.logConsentActivity(
|
|
1655
|
+
user_id,
|
|
1656
|
+
'granted',
|
|
1657
|
+
consent_type,
|
|
1658
|
+
{ consent_id: consent.consent_id },
|
|
1659
|
+
metadata
|
|
1660
|
+
);
|
|
1661
|
+
|
|
1662
|
+
return new Response(JSON.stringify({
|
|
1663
|
+
success: true,
|
|
1664
|
+
data: consent
|
|
1665
|
+
}), {
|
|
1666
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1667
|
+
});
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
return new Response(JSON.stringify({
|
|
1670
|
+
success: false,
|
|
1671
|
+
error: error.message
|
|
1672
|
+
}), {
|
|
1673
|
+
status: 500,
|
|
1674
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/**
|
|
1680
|
+
* Handle /api/compliance/consent/revoke
|
|
1681
|
+
*/
|
|
1682
|
+
async function handleRevokeConsent(request, env) {
|
|
1683
|
+
try {
|
|
1684
|
+
const body = await request.json();
|
|
1685
|
+
const { user_id, consent_type } = body;
|
|
1686
|
+
|
|
1687
|
+
if (!user_id || !consent_type) {
|
|
1688
|
+
return new Response(JSON.stringify({
|
|
1689
|
+
success: false,
|
|
1690
|
+
error: 'Missing required fields: user_id, consent_type'
|
|
1691
|
+
}), {
|
|
1692
|
+
status: 400,
|
|
1693
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
const metadata = {
|
|
1698
|
+
ip_address: request.headers.get('CF-Connecting-IP'),
|
|
1699
|
+
user_agent: request.headers.get('User-Agent'),
|
|
1700
|
+
source: body.source || 'api'
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1703
|
+
const consent = await consentManager.revokeConsent(user_id, consent_type, metadata);
|
|
1704
|
+
|
|
1705
|
+
await complianceAuditLogger.logConsentActivity(
|
|
1706
|
+
user_id,
|
|
1707
|
+
'revoked',
|
|
1708
|
+
consent_type,
|
|
1709
|
+
{ consent_id: consent.consent_id },
|
|
1710
|
+
metadata
|
|
1711
|
+
);
|
|
1712
|
+
|
|
1713
|
+
return new Response(JSON.stringify({
|
|
1714
|
+
success: true,
|
|
1715
|
+
data: consent
|
|
1716
|
+
}), {
|
|
1717
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1718
|
+
});
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
return new Response(JSON.stringify({
|
|
1721
|
+
success: false,
|
|
1722
|
+
error: error.message
|
|
1723
|
+
}), {
|
|
1724
|
+
status: 500,
|
|
1725
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* Handle /api/compliance/consent/check
|
|
1732
|
+
*/
|
|
1733
|
+
async function handleCheckConsent(request, env) {
|
|
1734
|
+
const url = new URL(request.url);
|
|
1735
|
+
const user_id = url.searchParams.get('user_id');
|
|
1736
|
+
const consent_type = url.searchParams.get('consent_type');
|
|
1737
|
+
|
|
1738
|
+
if (!user_id || !consent_type) {
|
|
1739
|
+
return new Response(JSON.stringify({
|
|
1740
|
+
success: false,
|
|
1741
|
+
error: 'Missing required parameters: user_id, consent_type'
|
|
1742
|
+
}), {
|
|
1743
|
+
status: 400,
|
|
1744
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
const hasConsent = await consentManager.hasValidConsent(user_id, consent_type);
|
|
1749
|
+
|
|
1750
|
+
return new Response(JSON.stringify({
|
|
1751
|
+
success: true,
|
|
1752
|
+
data: {
|
|
1753
|
+
user_id,
|
|
1754
|
+
consent_type,
|
|
1755
|
+
has_valid_consent: hasConsent
|
|
1756
|
+
}
|
|
1757
|
+
}), {
|
|
1758
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* Handle /api/compliance/data-rights/deletion
|
|
1764
|
+
*/
|
|
1765
|
+
async function handleSubmitDeletionRequest(request, env) {
|
|
1766
|
+
try {
|
|
1767
|
+
const body = await request.json();
|
|
1768
|
+
const { user_id, reason } = body;
|
|
1769
|
+
|
|
1770
|
+
if (!user_id) {
|
|
1771
|
+
return new Response(JSON.stringify({
|
|
1772
|
+
success: false,
|
|
1773
|
+
error: 'Missing required field: user_id'
|
|
1774
|
+
}), {
|
|
1775
|
+
status: 400,
|
|
1776
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
const metadata = {
|
|
1781
|
+
ip_address: request.headers.get('CF-Connecting-IP'),
|
|
1782
|
+
user_agent: request.headers.get('User-Agent')
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1785
|
+
const requestId = await dataRightsManager.submitDeletionRequest(user_id, reason, metadata);
|
|
1786
|
+
|
|
1787
|
+
await complianceAuditLogger.logDataRightsActivity(
|
|
1788
|
+
user_id,
|
|
1789
|
+
'deletion',
|
|
1790
|
+
requestId,
|
|
1791
|
+
{ reason },
|
|
1792
|
+
metadata
|
|
1793
|
+
);
|
|
1794
|
+
|
|
1795
|
+
return new Response(JSON.stringify({
|
|
1796
|
+
success: true,
|
|
1797
|
+
data: {
|
|
1798
|
+
request_id: requestId,
|
|
1799
|
+
status: 'pending',
|
|
1800
|
+
message: 'Deletion request submitted. Processing will begin shortly.'
|
|
1801
|
+
}
|
|
1802
|
+
}), {
|
|
1803
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1804
|
+
});
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
return new Response(JSON.stringify({
|
|
1807
|
+
success: false,
|
|
1808
|
+
error: error.message
|
|
1809
|
+
}), {
|
|
1810
|
+
status: 500,
|
|
1811
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Handle /api/compliance/data-rights/process
|
|
1818
|
+
*/
|
|
1819
|
+
async function handleProcessDataRightsRequest(request, env) {
|
|
1820
|
+
try {
|
|
1821
|
+
const body = await request.json();
|
|
1822
|
+
const { request_id, format } = body;
|
|
1823
|
+
|
|
1824
|
+
if (!request_id) {
|
|
1825
|
+
return new Response(JSON.stringify({
|
|
1826
|
+
success: false,
|
|
1827
|
+
error: 'Missing required field: request_id'
|
|
1828
|
+
}), {
|
|
1829
|
+
status: 400,
|
|
1830
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// Get request details
|
|
1835
|
+
const requestDetails = await env.DB.prepare(`
|
|
1836
|
+
SELECT * FROM deletion_requests WHERE request_id = ?
|
|
1837
|
+
`).bind(request_id).first();
|
|
1838
|
+
|
|
1839
|
+
if (!requestDetails) {
|
|
1840
|
+
return new Response(JSON.stringify({
|
|
1841
|
+
success: false,
|
|
1842
|
+
error: 'Request not found'
|
|
1843
|
+
}), {
|
|
1844
|
+
status: 404,
|
|
1845
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
let result;
|
|
1850
|
+
switch (requestDetails.request_type) {
|
|
1851
|
+
case 'access':
|
|
1852
|
+
result = await dataRightsManager.processAccessRequest(request_id);
|
|
1853
|
+
break;
|
|
1854
|
+
case 'deletion':
|
|
1855
|
+
result = await dataRightsManager.processDeletionRequest(request_id);
|
|
1856
|
+
break;
|
|
1857
|
+
case 'portability':
|
|
1858
|
+
result = await dataRightsManager.processPortabilityRequest(request_id, format);
|
|
1859
|
+
break;
|
|
1860
|
+
case 'rectification':
|
|
1861
|
+
result = await dataRightsManager.processRectificationRequest(request_id);
|
|
1862
|
+
break;
|
|
1863
|
+
default:
|
|
1864
|
+
return new Response(JSON.stringify({
|
|
1865
|
+
success: false,
|
|
1866
|
+
error: 'Unknown request type'
|
|
1867
|
+
}), {
|
|
1868
|
+
status: 400,
|
|
1869
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
return new Response(JSON.stringify({
|
|
1874
|
+
success: true,
|
|
1875
|
+
data: result
|
|
1876
|
+
}), {
|
|
1877
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1878
|
+
});
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
return new Response(JSON.stringify({
|
|
1881
|
+
success: false,
|
|
1882
|
+
error: error.message
|
|
1883
|
+
}), {
|
|
1884
|
+
status: 500,
|
|
1885
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
/**
|
|
1891
|
+
* Handle /api/compliance/retention/cleanup
|
|
1892
|
+
*/
|
|
1893
|
+
async function handleRetentionCleanup(request, env) {
|
|
1894
|
+
try {
|
|
1895
|
+
const result = await dataRetentionManager.executeRetentionCleanup();
|
|
1896
|
+
|
|
1897
|
+
await complianceAuditLogger.logRetentionActivity(
|
|
1898
|
+
'cleanup',
|
|
1899
|
+
{
|
|
1900
|
+
total_records_deleted: result.total_records_deleted,
|
|
1901
|
+
tables_cleaned: result.tables_cleaned
|
|
1902
|
+
}
|
|
1903
|
+
);
|
|
1904
|
+
|
|
1905
|
+
return new Response(JSON.stringify({
|
|
1906
|
+
success: true,
|
|
1907
|
+
data: result
|
|
1908
|
+
}), {
|
|
1909
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1910
|
+
});
|
|
1911
|
+
} catch (error) {
|
|
1912
|
+
return new Response(JSON.stringify({
|
|
1913
|
+
success: false,
|
|
1914
|
+
error: error.message
|
|
1915
|
+
}), {
|
|
1916
|
+
status: 500,
|
|
1917
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* Handle /api/compliance/retention/stats
|
|
1924
|
+
*/
|
|
1925
|
+
async function handleRetentionStats(request, env) {
|
|
1926
|
+
const stats = await dataRetentionManager.getRetentionStats();
|
|
1927
|
+
|
|
1928
|
+
return new Response(JSON.stringify({
|
|
1929
|
+
success: true,
|
|
1930
|
+
data: stats
|
|
1931
|
+
}), {
|
|
1932
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* Handle /api/compliance/audit/user
|
|
1938
|
+
*/
|
|
1939
|
+
async function handleGetUserAuditLogs(request, env) {
|
|
1940
|
+
const url = new URL(request.url);
|
|
1941
|
+
const user_id = url.searchParams.get('user_id');
|
|
1942
|
+
const limit = parseInt(url.searchParams.get('limit') || '100');
|
|
1943
|
+
|
|
1944
|
+
if (!user_id) {
|
|
1945
|
+
return new Response(JSON.stringify({
|
|
1946
|
+
success: false,
|
|
1947
|
+
error: 'Missing required parameter: user_id'
|
|
1948
|
+
}), {
|
|
1949
|
+
status: 400,
|
|
1950
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const logs = await complianceAuditLogger.getUserAuditLogs(user_id, limit);
|
|
1955
|
+
|
|
1956
|
+
return new Response(JSON.stringify({
|
|
1957
|
+
success: true,
|
|
1958
|
+
data: logs
|
|
1959
|
+
}), {
|
|
1960
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/**
|
|
1965
|
+
* Handle /api/compliance/audit/recent
|
|
1966
|
+
*/
|
|
1967
|
+
async function handleGetRecentAuditLogs(request, env) {
|
|
1968
|
+
const url = new URL(request.url);
|
|
1969
|
+
const limit = parseInt(url.searchParams.get('limit') || '50');
|
|
1970
|
+
|
|
1971
|
+
const logs = await complianceAuditLogger.getRecentAuditLogs(limit);
|
|
1972
|
+
|
|
1973
|
+
return new Response(JSON.stringify({
|
|
1974
|
+
success: true,
|
|
1975
|
+
data: logs
|
|
1976
|
+
}), {
|
|
1977
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
/**
|
|
1982
|
+
* Handle /api/compliance/policy/current
|
|
1983
|
+
*/
|
|
1984
|
+
async function handleGetCurrentPolicy(request, env) {
|
|
1985
|
+
const policy = await env.DB.prepare(`
|
|
1986
|
+
SELECT * FROM consent_policy_versions
|
|
1987
|
+
ORDER BY effective_date DESC
|
|
1988
|
+
LIMIT 1
|
|
1989
|
+
`).first();
|
|
1990
|
+
|
|
1991
|
+
return new Response(JSON.stringify({
|
|
1992
|
+
success: true,
|
|
1993
|
+
data: policy
|
|
1994
|
+
}), {
|
|
1995
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
```
|
|
1999
|
+
|
|
2000
|
+
---
|
|
2001
|
+
|
|
2002
|
+
## 📋 Resumo de Implementação
|
|
2003
|
+
|
|
2004
|
+
### Componentes Criados
|
|
2005
|
+
|
|
2006
|
+
1. **ConsentManager** - Gestão completa de consentimento (GDPR, LGPD, CCPA)
|
|
2007
|
+
2. **DataRightsManager** - Implementação de todos os direitos dos titulares
|
|
2008
|
+
3. **DataRetentionManager** - Políticas de retenção automatizadas
|
|
2009
|
+
4. **ComplianceAuditLogger** - Trilhas de auditoria completas
|
|
2010
|
+
|
|
2011
|
+
### Endpoints de Compliance
|
|
2012
|
+
|
|
2013
|
+
| Endpoint | Método | Descrição |
|
|
2014
|
+
|----------|--------|-----------|
|
|
2015
|
+
| `/api/compliance/consent/grant` | POST | Conceder consentimento |
|
|
2016
|
+
| `/api/compliance/consent/revoke` | POST | Revogar consentimento |
|
|
2017
|
+
| `/api/compliance/consent/check` | GET | Verificar consentimento válido |
|
|
2018
|
+
| `/api/compliance/consent/preferences` | GET/POST | Gerenciar preferências de cookies |
|
|
2019
|
+
| `/api/compliance/data-rights/access` | POST | Solicitar acesso aos dados |
|
|
2020
|
+
| `/api/compliance/data-rights/deletion` | POST | Solicitar exclusão de dados |
|
|
2021
|
+
| `/api/compliance/data-rights/portability` | POST | Solicitar portabilidade de dados |
|
|
2022
|
+
| `/api/compliance/data-rights/rectification` | POST | Solicitar correção de dados |
|
|
2023
|
+
| `/api/compliance/data-rights/process` | POST | Processar solicitação de direitos |
|
|
2024
|
+
| `/api/compliance/retention/cleanup` | POST | Executar limpeza de retenção |
|
|
2025
|
+
| `/api/compliance/retention/stats` | GET | Estatísticas de retenção |
|
|
2026
|
+
| `/api/compliance/audit/user` | GET | Logs de auditoria por usuário |
|
|
2027
|
+
| `/api/compliance/audit/recent` | GET | Logs de auditoria recentes |
|
|
2028
|
+
| `/api/compliance/policy/current` | GET | Política de consentimento atual |
|
|
2029
|
+
|
|
2030
|
+
### Funcionalidades de Compliance
|
|
2031
|
+
|
|
2032
|
+
- **GDPR Compliance**: Todos os requisitos da Regulamentação Geral de Proteção de Dados (UE)
|
|
2033
|
+
- **LGPD Compliance**: Todos os requisitos da Lei Geral de Proteção de Dados (Brasil)
|
|
2034
|
+
- **CCPA Compliance**: Todos os requisitos da California Consumer Privacy Act (EUA)
|
|
2035
|
+
- **Consent Management**: Sistema completo de gestão de consentimento com histórico
|
|
2036
|
+
- **Data Rights**: Acesso, exclusão, portabilidade e correção de dados
|
|
2037
|
+
- **Data Retention**: Políticas automatizadas de retenção e exclusão
|
|
2038
|
+
- **Audit Trails**: Trilhas de auditoria completas para compliance
|
|
2039
|
+
|
|
2040
|
+
---
|
|
2041
|
+
|
|
2042
|
+
## 🔗 Integração com Outros Agentes
|
|
2043
|
+
|
|
2044
|
+
- **security-enterprise-agent.md**: Usa encryption de PII para LGPD/GDPR
|
|
2045
|
+
- **attribution-agent.md**: Respeita consentimento para analytics e marketing
|
|
2046
|
+
- **master-orchestrator.md**: Implementa middleware de consentimento antes de tracking
|
|
2047
|
+
|
|
2048
|
+
---
|
|
2049
|
+
|
|
2050
|
+
## ✅ Checklist de Implementação
|
|
2051
|
+
|
|
2052
|
+
- [x] Implementar ConsentManager com grant/revoke/check
|
|
2053
|
+
- [x] Implementar DataRightsManager (access, deletion, portability, rectification)
|
|
2054
|
+
- [x] Implementar DataRetentionManager com políticas automatizadas
|
|
2055
|
+
- [x] Implementar ComplianceAuditLogger com trilhas completas
|
|
2056
|
+
- [x] Criar D1 schemas para consent_records, consent_history, cookie_preferences
|
|
2057
|
+
- [x] Criar D1 schemas para deletion_requests, retention_policies
|
|
2058
|
+
- [x] Criar endpoints de API para todas as funcionalidades de compliance
|
|
2059
|
+
- [x] Implementar GDPR compliance (Art. 15, 16, 17, 20)
|
|
2060
|
+
- [x] Implementar LGPD compliance (Art. 18)
|
|
2061
|
+
- [x] Implementar CCPA compliance (access, deletion, opt-out)
|
|
2062
|
+
- [x] Implementar cookie preferences management
|
|
2063
|
+
- [x] Implementar data retention policies automatizadas
|
|
2064
|
+
- [x] Implementar audit trails para todas as atividades de compliance
|
|
2065
|
+
|
|
2066
|
+
---
|
|
2067
|
+
|
|
2068
|
+
## 📚 Referências
|
|
2069
|
+
|
|
2070
|
+
- [GDPR Official Text](https://gdpr-info.eu/)
|
|
2071
|
+
- [LGPD Texto Completo](https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm)
|
|
2072
|
+
- [CCPA Official Text](https://leginfo.legislature.ca.gov/faces/codes_displayText.xhtml?lawCode=CIV§ionId=1798.100)
|
|
2073
|
+
- [IAB Europe Transparency & Consent Framework](https://iabeurope.eu/transparency-consent-framework/)
|
|
2074
|
+
|
|
2075
|
+
---
|
|
2076
|
+
|
|
2077
|
+
*Compliance Enterprise Agent v1.0.0 - CDP Edge Quantum Tier*
|