Tutorial 2: Implementing Data Subject Rights Dashboard
Tutorial 2: Implementing Data Subject Rights Dashboard
This tutorial creates a comprehensive dashboard where users can exercise all their privacy rights - access, deletion, portability, and more. The implementation includes identity verification, request processing, and automated fulfillment where possible.
// Frontend: Privacy rights dashboard
class PrivacyRightsDashboard {
constructor() {
this.apiBase = '/api/privacy/rights';
this.currentRequests = [];
this.init();
}
async init() {
await this.loadExistingRequests();
this.renderDashboard();
this.setupEventHandlers();
}
renderDashboard() {
const container = document.getElementById('privacy-dashboard');
container.innerHTML = `
<div class="privacy-dashboard">
<h1>Your Privacy Rights</h1>
<div class="rights-grid">
<div class="right-card" data-right="access">
<h3>Right to Access</h3>
<p>Download a copy of all your personal data</p>
<button class="btn-primary" data-action="request-access">
Request My Data
</button>
</div>
<div class="right-card" data-right="deletion">
<h3>Right to Delete</h3>
<p>Request deletion of your personal data</p>
<button class="btn-danger" data-action="request-deletion">
Delete My Data
</button>
</div>
<div class="right-card" data-right="portability">
<h3>Right to Portability</h3>
<p>Export your data in a portable format</p>
<button class="btn-primary" data-action="request-export">
Export Data
</button>
</div>
<div class="right-card" data-right="rectification">
<h3>Right to Rectification</h3>
<p>Correct inaccurate personal data</p>
<button class="btn-primary" data-action="update-data">
Update My Data
</button>
</div>
</div>
<div class="active-requests">
<h2>Active Requests</h2>
<div id="requests-list"></div>
</div>
<div class="privacy-settings">
<h2>Privacy Settings</h2>
<div id="privacy-controls"></div>
</div>
</div>
`;
this.renderActiveRequests();
this.renderPrivacyControls();
}
renderActiveRequests() {
const container = document.getElementById('requests-list');
if (this.currentRequests.length === 0) {
container.innerHTML = '<p>No active requests</p>';
return;
}
const requestsHTML = this.currentRequests.map(request => `
<div class="request-item" data-request-id="${request.id}">
<div class="request-header">
<h4>${this.getRequestTypeLabel(request.type)}</h4>
<span class="request-date">${this.formatDate(request.createdAt)}</span>
</div>
<div class="request-status">
<span class="status-badge status-${request.status}">
${request.status}
</span>
${this.getRequestProgress(request)}
</div>
${this.getRequestActions(request)}
</div>
`).join('');
container.innerHTML = requestsHTML;
}
async handleAccessRequest() {
const modal = this.createModal({
title: 'Request Your Data',
content: `
<p>We'll prepare a complete copy of your personal data. This includes:</p>
<ul>
<li>Profile information</li>
<li>Activity history</li>
<li>Preferences and settings</li>
<li>Content you've created</li>
</ul>
<p>For security, we'll need to verify your identity first.</p>
<div class="form-group">
<label>Preferred format:</label>
<select id="export-format">
<option value="json">JSON (recommended)</option>
<option value="csv">CSV</option>
<option value="xml">XML</option>
</select>
</div>
`,
actions: [
{
label: 'Cancel',
class: 'btn-secondary',
handler: () => this.closeModal()
},
{
label: 'Proceed to Verification',
class: 'btn-primary',
handler: () => this.startVerification('access')
}
]
});
this.showModal(modal);
}
async startVerification(requestType) {
const verificationUI = new VerificationUI();
try {
const verificationResult = await verificationUI.verify({
purpose: `${requestType} request`,
methods: ['email', 'sms', 'authenticator']
});
if (verificationResult.success) {
await this.submitRequest(requestType, verificationResult);
}
} catch (error) {
this.showError('Verification failed. Please try again.');
}
}
async submitRequest(type, verification) {
const requestData = {
type,
verification: verification.token,
preferences: this.getRequestPreferences(type),
timestamp: new Date().toISOString()
};
try {
const response = await fetch(`${this.apiBase}/request`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
if (!response.ok) throw new Error('Request failed');
const result = await response.json();
this.showSuccess(`Your ${type} request has been submitted.
Estimated completion: ${result.estimatedCompletion}`);
// Add to active requests
this.currentRequests.push(result.request);
this.renderActiveRequests();
} catch (error) {
this.showError('Failed to submit request. Please try again.');
}
}
}
// Backend: Rights request processing
class RightsRequestProcessor {
constructor() {
this.queue = new RequestQueue();
this.handlers = new Map();
this.setupHandlers();
}
setupHandlers() {
this.handlers.set('access', new AccessRequestHandler());
this.handlers.set('deletion', new DeletionRequestHandler());
this.handlers.set('portability', new PortabilityRequestHandler());
this.handlers.set('rectification', new RectificationRequestHandler());
}
async processRequest(request) {
// Update status
await this.updateRequestStatus(request.id, 'processing');
try {
// Get appropriate handler
const handler = this.handlers.get(request.type);
if (!handler) {
throw new Error(`Unknown request type: ${request.type}`);
}
// Process request
const result = await handler.process(request);
// Store result
await this.storeResult(request.id, result);
// Update status
await this.updateRequestStatus(request.id, 'completed');
// Notify user
await this.notifyCompletion(request, result);
return result;
} catch (error) {
await this.handleProcessingError(request, error);
throw error;
}
}
}
// Access request handler
class AccessRequestHandler {
async process(request) {
const userId = request.userId;
const data = {};
// Gather data from all sources
const dataSources = [
{ name: 'profile', handler: this.getProfileData },
{ name: 'activity', handler: this.getActivityData },
{ name: 'content', handler: this.getContentData },
{ name: 'preferences', handler: this.getPreferencesData },
{ name: 'consent', handler: this.getConsentData },
{ name: 'thirdParty', handler: this.getThirdPartyData }
];
for (const source of dataSources) {
try {
data[source.name] = await source.handler(userId);
} catch (error) {
console.error(`Failed to get ${source.name} data:`, error);
data[source.name] = { error: 'Failed to retrieve' };
}
}
// Generate report
const report = await this.generateReport(data, request);
// Create secure package
const package = await this.createSecurePackage(report, request);
return {
type: 'access_report',
package,
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days
};
}
async generateReport(data, request) {
const report = {
metadata: {
reportId: generateId(),
generatedAt: new Date().toISOString(),
requestId: request.id,
format: request.preferences.format || 'json'
},
summary: {
dataCategories: Object.keys(data),
recordCount: this.countRecords(data),
dateRange: await this.getDateRange(request.userId)
},
data
};
// Add processing information
report.processingInfo = await this.getProcessingInfo(request.userId);
// Add third-party sharing info
report.thirdPartySharing = await this.getThirdPartyInfo(request.userId);
return report;
}
async createSecurePackage(report, request) {
// Convert to requested format
const formatted = await this.formatReport(report, request.preferences.format);
// Encrypt with user's key
const encrypted = await this.encryptPackage(formatted, request.userId);
// Create download package
const package = {
id: generateId(),
encryptedData: encrypted,
checksum: await this.calculateChecksum(encrypted),
format: request.preferences.format,
instructions: this.getDecryptionInstructions()
};
// Store temporarily
await this.storePackage(package);
return package;
}
}