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;
  }
}