Skip to content
Back to skills

Gdpr Mauriciodelrio

ASecurity

Use this skill when the software processes personal data from users. Although GDPR is EU legislation, this skill applies as a data protection standard for any software regardless of the geographic location of users. Covers granular consent system, data subject rights (ARCO+), encryption and pseudonymization, retention policy, cookie banner, and compliance checklist.

  • 85 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 8, 2026
ai-agentstypescriptgonodeexpresstestinggitapidatabasesecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 8, 2026

npx -y skills add ThomasMoreAI/legal-skills-open --skill gdpr-mauriciodelrio --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gdpr Mauriciodelrio?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gdpr Mauriciodelrio
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/thomasmoreai-gdpr-mauriciodelrio/badge)](https://www.skillsdirectory.com/skills/thomasmoreai-gdpr-mauriciodelrio)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: gdpr-mauriciodelrio
title: GDPR — General Data Protection Regulation
description: Use this skill when the software processes personal data from users. Although GDPR is EU legislation, this skill applies as a data protection standard for any software regardless of the geographic location of users. Covers granular consent system, data subject rights (ARCO+), encryption and pseudonymization, retention policy, cookie banner, and compliance checklist.
author: mauriciodelrio
author_url: https://github.com/mauriciodelrio/delriodev-skills/tree/main/en-skills/governance-risk-and-compliance/gdpr
license: MIT
version: 0.1.0
execution_mode: open
jurisdiction: eu
practice: data-protection
language: en
---

# GDPR — General Data Protection Regulation

GDPR (EU Regulation 2016/679) defines the most rigorous data protection standard globally and should be applied to any software processing personal data, regardless of user geography. Every implementation must respect the 7 principles of Art. 5: lawfulness/fairness/transparency, purpose limitation, data minimization, accuracy, storage limitation, integrity/confidentiality, and accountability. Each processing activity requires a valid legal basis (Art. 6): consent, contract, legal obligation, vital interests, public interest, or legitimate interest. Sensitive data (Art. 9) requires additional legal bases.

## Implementation

### 1. Consent System

#### Database Schema (Prisma)

```prisma
// schema.prisma — Granular consent model

model User {
  id            String    @id @default(cuid())
  email         String    @unique
  // Personal data with protection metadata
  firstName     String?
  lastName      String?
  phone         String?
  dateOfBirth   DateTime?
  
  // Compliance relations
  consents      Consent[]
  dataRequests  DataSubjectRequest[]
  auditLogs     AuditLog[]
  
  // Creation and retention metadata
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  deletedAt     DateTime? // Soft delete for retention period
  retentionExpiresAt DateTime? // Retention expiration date
  
  @@map("users")
}

model Consent {
  id            String    @id @default(cuid())
  userId        String
  user          User      @relation(fields: [userId], references: [id])
  
  // Granular consent type
  purpose       ConsentPurpose
  legalBasis    LegalBasis
  
  // Status and traceability
  granted       Boolean
  grantedAt     DateTime?
  revokedAt     DateTime?
  expiresAt     DateTime?
  
  // Consent evidence
  ipAddress     String?   // IP at the time of consent
  userAgent     String?   // Browser/device
  consentText   String    // Exact text the user accepted
  version       String    // Privacy policy version
  source        String    // Where it was obtained (web, app, api)
  
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  
  @@unique([userId, purpose]) // One consent per purpose per user
  @@index([userId])
  @@index([purpose])
  @@map("consents")
}

enum ConsentPurpose {
  ESSENTIAL           // Basic service functionality
  ANALYTICS           // Usage analysis and metrics
  MARKETING_EMAIL     // Marketing communications by email
  MARKETING_PUSH      // Marketing push notifications
  THIRD_PARTY_SHARING // Sharing data with third parties
  PROFILING           // User profile creation
  COOKIES_FUNCTIONAL  // Functional cookies
  COOKIES_ANALYTICS   // Analytics cookies
  COOKIES_ADVERTISING // Advertising cookies
}

enum LegalBasis {
  CONSENT
  CONTRACT
  LEGAL_OBLIGATION
  VITAL_INTERESTS
  PUBLIC_INTEREST
  LEGITIMATE_INTEREST
}
```

#### Consent Service

```typescript
// services/consent.service.ts

import { PrismaClient, ConsentPurpose, LegalBasis } from '@prisma/client';
import { z } from 'zod';
import pino from 'pino';

const prisma = new PrismaClient();
const logger = pino({ name: 'consent-service' });

// Validation schema for granting consent
const GrantConsentSchema = z.object({
  userId: z.string().cuid(),
  purpose: z.nativeEnum(ConsentPurpose),
  legalBasis: z.nativeEnum(LegalBasis),
  consentText: z.string().min(10),
  version: z.string(),
  source: z.enum(['web', 'mobile_app', 'api']),
});

type GrantConsentInput = z.infer<typeof GrantConsentSchema>;

interface ConsentContext {
  ipAddress: string;
  userAgent: string;
}

export class ConsentService {
  /**
   * Grant consent in a granular manner.
   * GDPR Art. 7: Consent must be freely given, specific, informed, and unambiguous.
   */
  async grantConsent(input: GrantConsentInput, context: ConsentContext) {
    const validated = GrantConsentSchema.parse(input);

    const consent = await prisma.consent.upsert({
      where: {
        userId_purpose: {
          userId: validated.userId,
          purpose: validated.purpose,
        },
      },
      create: {
        userId: validated.userId,
        purpose: validated.purpose,
        legalBasis: validated.legalBasis,
        granted: true,
        grantedAt: new Date(),
        consentText: validated.consentText,
        version: validated.version,
        source: validated.source,
        ipAddress: context.ipAddress,
        userAgent: context.userAgent,
      },
      update: {
        granted: true,
        grantedAt: new Date(),
        revokedAt: null,
        consentText: validated.consentText,
        version: validated.version,
        source: validated.source,
        ipAddress: context.ipAddress,
        userAgent: context.userAgent,
      },
    });

    logger.info({
      event: 'consent_granted',
      userId: validated.userId,
      purpose: validated.purpose,
      legalBasis: validated.legalBasis,
      version: validated.version,
    }, 'Consent granted');

    return consent;
  }

  /**
   * Revoke consent.
   * GDPR Art. 7(3): It must be as easy to withdraw as to give consent.
   */
  async revokeConsent(userId: string, purpose: ConsentPurpose) {
    const consent = await prisma.consent.update({
      where: {
        userId_purpose: { userId, purpose },
      },
      data: {
        granted: false,
        revokedAt: new Date(),
      },
    });

    logger.info({
      event: 'consent_revoked',
      userId,
      purpose,
    }, 'Consent revoked');

    // Trigger cleanup of data associated with this purpose
    await this.triggerDataCleanup(userId, purpose);

    return consent;
  }

  /**
   * Check if a user has active consent for a purpose.
   */
  async hasConsent(userId: string, purpose: ConsentPurpose): Promise<boolean> {
    const consent = await prisma.consent.findUnique({
      where: {
        userId_purpose: { userId, purpose },
      },
    });

    if (!consent) return false;
    if (!consent.granted) return false;
    if (consent.revokedAt) return false;
    if (consent.expiresAt && consent.expiresAt < new Date()) return false;

    return true;
  }

  /**
   * Get all consents for a user (for privacy panel).
   */
  async getUserConsents(userId: string) {
    return prisma.consent.findMany({
      where: { userId },
      orderBy: { purpose: 'asc' },
    });
  }

  private async triggerDataCleanup(userId: string, purpose: ConsentPurpose) {
    // Implement cleanup logic based on the revoked purpose
    // For example: if ANALYTICS is revoked, delete tracking data
    // If MARKETING_EMAIL is revoked, unsubscribe from email lists
    logger.info({
      event: 'data_cleanup_triggered',
      userId,
      purpose,
    }, 'Data cleanup triggered by consent revocation');
  }
}
```

#### Consent Verification Middleware

```typescript
// middleware/consent.middleware.ts

import { Request, Response, NextFunction } from 'express';
import { ConsentPurpose } from '@prisma/client';
import { ConsentService } from '../services/consent.service';

const consentService = new ConsentService();

/**
 * Middleware factory that verifies consent before processing the request.
 * Use it on routes that require specific consent.
 * 
 * @example
 * router.post('/newsletter/subscribe', requireConsent(ConsentPurpose.MARKETING_EMAIL), handler);
 */
export function requireConsent(purpose: ConsentPurpose) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const userId = req.user?.id;

    if (!userId) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    const hasConsent = await consentService.hasConsent(userId, purpose);

    if (!hasConsent) {
      return res.status(403).json({
        error: 'Consent required',
        code: 'CONSENT_REQUIRED',
        purpose,
        message: `Consent required for: ${purpose}`,
        consentUrl: `/api/v1/privacy/consent`,
      });
    }

    next();
  };
}
```

---

### 2. Data Subject Rights (ARCO+ Rights)

#### Schema for Rights Requests

```prisma
// Add to schema.prisma

model DataSubjectRequest {
  id            String    @id @default(cuid())
  userId        String
  user          User      @relation(fields: [userId], references: [id])
  
  type          DSRType
  status        DSRStatus @default(PENDING)
  
  // Request details
  description   String?
  requestedAt   DateTime  @default(now())
  acknowledgedAt DateTime? // Receipt confirmation
  completedAt   DateTime?  // Resolution date
  deadline      DateTime   // Maximum deadline (30 days from request)
  
  // Response
  responseNotes String?
  responseData  Json?      // Exported data (for portability)
  
  // Audit
  processedBy   String?    // ID of the admin who processed
  
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  
  @@index([userId])
  @@index([status])
  @@index([deadline])
  @@map("data_subject_requests")
}

enum DSRType {
  ACCESS          // Art. 15 - Right of access
  RECTIFICATION   // Art. 16 - Right to rectification
  ERASURE         // Art. 17 - Right to be forgotten
  RESTRICTION     // Art. 18 - Right to restriction
  PORTABILITY     // Art. 20 - Right to data portability
  OBJECTION       // Art. 21 - Right to object
  AUTOMATED_DECISION // Art. 22 - Not to be subject to automated decisions
}

enum DSRStatus {
  PENDING
  ACKNOWLEDGED
  IN_PROGRESS
  COMPLETED
  REJECTED
  EXTENDED     // Extended deadline (max 2 additional months)
}
```

#### Data Subject Rights Service

```typescript
// services/data-subject-rights.service.ts

import { PrismaClient, DSRType, DSRStatus } from '@prisma/client';
import pino from 'pino';
import crypto from 'node:crypto';

const prisma = new PrismaClient();
const logger = pino({ name: 'dsr-service' });

// GDPR deadline: 30 calendar days from the request
const GDPR_DEADLINE_DAYS = 30;
const GDPR_EXTENSION_DAYS = 60; // Maximum additional extension

export class DataSubjectRightsService {
  /**
   * Create a rights request.
   * GDPR Art. 12(3): Respond without undue delay, maximum 1 month.
   */
  async createRequest(userId: string, type: DSRType, description?: string) {
    const deadline = new Date();
    deadline.setDate(deadline.getDate() + GDPR_DEADLINE_DAYS);

    const request = await prisma.dataSubjectRequest.create({
      data: {
        userId,
        type,
        description,
        deadline,
        acknowledgedAt: new Date(), // Confirm immediate receipt
        status: DSRStatus.ACKNOWLEDGED,
      },
    });

    logger.info({
      event: 'dsr_created',
      requestId: request.id,
      userId,
      type,
      deadline: deadline.toISOString(),
    }, 'Data subject rights request created');

    return request;
  }

  /**
   * Right of Access (Art. 15).
   * Export ALL personal data of the user in structured format.
   */
  async processAccessRequest(requestId: string, processedBy: string) {
    const request = await prisma.dataSubjectRequest.findUniqueOrThrow({
      where: { id: requestId },
      include: { user: true },
    });

    // Collect all personal data for the user
    const userData = await this.collectAllUserData(request.userId);

    await prisma.dataSubjectRequest.update({
      where: { id: requestId },
      data: {
        status: DSRStatus.COMPLETED,
        completedAt: new Date(),
        processedBy,
        responseData: userData,
      },
    });

    logger.info({
      event: 'dsr_access_completed',
      requestId,
      userId: request.userId,
      processedBy,
    }, 'Access request completed');

    return userData;
  }

  /**
   * Right to be Forgotten (Art. 17).
   * Delete all personal data of the user.
   * Exceptions: legal obligations, defense of claims.
   */
  async processErasureRequest(requestId: string, processedBy: string) {
    const request = await prisma.dataSubjectRequest.findUniqueOrThrow({
      where: { id: requestId },
    });

    // Check if there are legal exceptions that prevent deletion
    const exceptions = await this.checkErasureExceptions(request.userId);
    
    if (exceptions.length > 0) {
      await prisma.dataSubjectRequest.update({
        where: { id: requestId },
        data: {
          status: DSRStatus.REJECTED,
          responseNotes: `Partial deletion. Legal exceptions: ${exceptions.join(', ')}`,
          processedBy,
        },
      });

      logger.warn({
        event: 'dsr_erasure_partial',
        requestId,
        userId: request.userId,
        exceptions,
      }, 'Partial deletion due to legal exceptions');

      // Delete only data without legal restriction
      await this.partialErasure(request.userId, exceptions);
      return { partial: true, exceptions };
    }

    // Full deletion
    await this.fullErasure(request.userId);

    await prisma.dataSubjectRequest.update({
      where: { id: requestId },
      data: {
        status: DSRStatus.COMPLETED,
        completedAt: new Date(),
        processedBy,
      },
    });

    logger.info({
      event: 'dsr_erasure_completed',
      requestId,
      userId: request.userId,
      processedBy,
    }, 'Erasure request completed');

    return { partial: false };
  }

  /**
   * Right to Data Portability (Art. 20).
   * Export data in a structured, commonly used, and machine-readable format.
   */
  async processPortabilityRequest(requestId: string, processedBy: string) {
    const request = await prisma.dataSubjectRequest.findUniqueOrThrow({
      where: { id: requestId },
    });

    const userData = await this.collectAllUserData(request.userId);

    // Structured JSON format (machine-readable)
    const portableData = {
      exportMetadata: {
        format: 'JSON',
        exportDate: new Date().toISOString(),
        dataController: process.env.DATA_CONTROLLER_NAME,
        requestId,
      },
      personalData: userData,
    };

    await prisma.dataSubjectRequest.update({
      where: { id: requestId },
      data: {
        status: DSRStatus.COMPLETED,
        completedAt: new Date(),
        processedBy,
        responseData: portableData,
      },
    });

    return portableData;
  }

  /**
   * Collect all personal data of the user from all tables.
   */
  private async collectAllUserData(userId: string) {
    const [user, consents, orders, activityLogs] = await Promise.all([
      prisma.user.findUnique({
        where: { id: userId },
        select: {
          id: true,
          email: true,
          firstName: true,
          lastName: true,
          phone: true,
          dateOfBirth: true,
          createdAt: true,
        },
      }),
      prisma.consent.findMany({
        where: { userId },
        select: {
          purpose: true,
          granted: true,
          grantedAt: true,
          revokedAt: true,
          version: true,
        },
      }),
      // Add more tables according to your data model
      // prisma.order.findMany({ where: { userId } }),
      // prisma.address.findMany({ where: { userId } }),
      Promise.resolve([]), // Placeholder for orders
      Promise.resolve([]), // Placeholder for activity logs
    ]);

    return {
      profile: user,
      consents,
      orders,
      activityLogs,
    };
  }

  /**
   * Check legal exceptions for deletion.
   * Example: invoices must be retained due to tax obligations.
   */
  private async checkErasureExceptions(userId: string): Promise<string[]> {
    const exceptions: string[] = [];

    // Example: Check if there are invoices (legal tax retention obligation)
    // const invoices = await prisma.invoice.count({ where: { userId } });
    // if (invoices > 0) {
    //   exceptions.push('invoices_tax_obligation');
    // }

    return exceptions;
  }

  private async fullErasure(userId: string) {
    await prisma.$transaction(async (tx) => {
      // Anonymize instead of hard-delete to maintain referential integrity
      await tx.user.update({
        where: { id: userId },
        data: {
          email: `deleted_${crypto.randomUUID()}@anonymized.local`,
          firstName: null,
          lastName: null,
          phone: null,
          dateOfBirth: null,
          deletedAt: new Date(),
        },
      });

      // Delete consents
      await tx.consent.deleteMany({ where: { userId } });
    });
  }

  private async partialErasure(userId: string, exceptions: string[]) {
    // Delete only data that has no legal restriction
    await prisma.user.update({
      where: { id: userId },
      data: {
        firstName: null,
        lastName: null,
        phone: null,
        dateOfBirth: null,
        // Keep email if needed for contractual obligations
      },
    });
  }
}
```

#### Rights API (Express Router)

```typescript
// routes/privacy.routes.ts

import { Router, Request, Response } from 'express';
import { z } from 'zod';
import { DSRType } from '@prisma/client';
import { DataSubjectRightsService } from '../services/data-subject-rights.service';
import { ConsentService } from '../services/consent.service';
import { authenticate } from '../middleware/auth.middleware';

const router = Router();
const dsrService = new DataSubjectRightsService();
const consentService = new ConsentService();

// All routes require authentication
router.use(authenticate);

/**
 * GET /api/v1/privacy/my-data
 * Get all personal data (Art. 15 - Access)
 */
router.get('/my-data', async (req: Request, res: Response) => {
  const request = await dsrService.createRequest(
    req.user!.id,
    DSRType.ACCESS,
    'Personal data access request via API'
  );
  
  // For automated requests, process immediately
  const data = await dsrService.processAccessRequest(request.id, 'system_auto');
  
  res.json({
    requestId: request.id,
    data,
    exportFormats: ['json', 'csv'],
    downloadUrl: `/api/v1/privacy/requests/${request.id}/download`,
  });
});

/**
 * POST /api/v1/privacy/erasure
 * Request data deletion (Art. 17 - Right to be forgotten)
 */
router.post('/erasure', async (req: Request, res: Response) => {
  const request = await dsrService.createRequest(
    req.user!.id,
    DSRType.ERASURE,
    req.body.reason
  );

  res.status(202).json({
    requestId: request.id,
    status: 'acknowledged',
    message: 'Your deletion request has been received. It will be processed within a maximum of 30 days.',
    deadline: request.deadline,
  });
});

/**
 * POST /api/v1/privacy/portability
 * Export data in portable format (Art. 20)
 */
router.post('/portability', async (req: Request, res: Response) => {
  const request = await dsrService.createRequest(
    req.user!.id,
    DSRType.PORTABILITY,
    'Data portability request'
  );

  const data = await dsrService.processPortabilityRequest(request.id, 'system_auto');

  res.json({
    requestId: request.id,
    format: 'application/json',
    data,
  });
});

/**
 * GET /api/v1/privacy/consents
 * View all user consents
 */
router.get('/consents', async (req: Request, res: Response) => {
  const consents = await consentService.getUserConsents(req.user!.id);
  res.json({ consents });
});

/**
 * POST /api/v1/privacy/consents
 * Grant a consent
 */
router.post('/consents', async (req: Request, res: Response) => {
  const consent = await consentService.grantConsent(
    { ...req.body, userId: req.user!.id },
    { ipAddress: req.ip ?? 'unknown', userAgent: req.headers['user-agent'] ?? 'unknown' }
  );
  res.status(201).json({ consent });
});

/**
 * DELETE /api/v1/privacy/consents/:purpose
 * Revoke a specific consent
 */
router.delete('/consents/:purpose', async (req: Request, res: Response) => {
  const purpose = req.params.purpose as any;
  const consent = await consentService.revokeConsent(req.user!.id, purpose);
  res.json({ consent, message: 'Consent successfully revoked' });
});

export default router;
```

---

### 3. Encryption and Pseudonymization

```typescript
// utils/encryption.service.ts

import crypto from 'node:crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
const SALT_LENGTH = 32;

/**
 * Encryption service for personal data at rest.
 * GDPR Art. 32: Appropriate technical measures, including encryption.
 */
export class EncryptionService {
  private readonly key: Buffer;

  constructor() {
    const masterKey = process.env.DATA_ENCRYPTION_KEY;
    if (!masterKey || masterKey.length < 32) {
      throw new Error('DATA_ENCRYPTION_KEY must be configured with at least 32 characters');
    }
    // Derive key using HKDF for greater security
    this.key = crypto.createHash('sha256').update(masterKey).digest();
  }

  /**
   * Encrypt a personal data field.
   * Returns string with format: iv:authTag:ciphertext (all in hex).
   */
  encrypt(plaintext: string): string {
    const iv = crypto.randomBytes(IV_LENGTH);
    const cipher = crypto.createCipheriv(ALGORITHM, this.key, iv, {
      authTagLength: AUTH_TAG_LENGTH,
    });

    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    const authTag = cipher.getAuthTag();

    return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
  }

  /**
   * Decrypt a personal data field.
   */
  decrypt(encryptedText: string): string {
    const [ivHex, authTagHex, ciphertext] = encryptedText.split(':');
    
    const iv = Buffer.from(ivHex, 'hex');
    const authTag = Buffer.from(authTagHex, 'hex');
    const decipher = crypto.createDecipheriv(ALGORITHM, this.key, iv, {
      authTagLength: AUTH_TAG_LENGTH,
    });
    decipher.setAuthTag(authTag);

    let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }

  /**
   * Pseudonymize personal data (reversible process with the key).
   * GDPR Art. 4(5): Processing in such a manner that it can no longer
   * be attributed without additional information.
   */
  pseudonymize(data: string): string {
    const hmac = crypto.createHmac('sha256', this.key);
    hmac.update(data);
    return hmac.digest('hex');
  }

  /**
   * Anonymize data (irreversible process).
   * Anonymized data is no longer personal data under GDPR.
   */
  static anonymize(data: string): string {
    const salt = crypto.randomBytes(SALT_LENGTH);
    return crypto.createHash('sha256').update(salt).update(data).digest('hex');
  }

  /**
   * Secure password hash (use bcrypt or argon2 in production).
   */
  static async hashPassword(password: string): Promise<string> {
    // In production use: import bcrypt from 'bcrypt'; return bcrypt.hash(password, 12);
    const salt = crypto.randomBytes(16).toString('hex');
    return new Promise((resolve, reject) => {
      crypto.scrypt(password, salt, 64, (err, derivedKey) => {
        if (err) reject(err);
        resolve(`${salt}:${derivedKey.toString('hex')}`);
      });
    });
  }
}
```

---

### 4. Data Retention Policy

```typescript
// jobs/data-retention.job.ts

import { PrismaClient } from '@prisma/client';
import pino from 'pino';

const prisma = new PrismaClient();
const logger = pino({ name: 'data-retention' });

/**
 * Data retention policy per GDPR Art. 5(1)(e).
 * "Storage limitation": data shall not be kept longer than
 * necessary for the purposes of the processing.
 * 
 * Run as a daily cron job.
 */

interface RetentionPolicy {
  model: string;
  field: string;         // Date field to evaluate retention
  retentionDays: number; // Retention days
  action: 'anonymize' | 'delete' | 'archive';
  legalBasis: string;    // Legal justification for retention
}

const RETENTION_POLICIES: RetentionPolicy[] = [
  {
    model: 'auditLog',
    field: 'createdAt',
    retentionDays: 365 * 2, // 2 years
    action: 'archive',
    legalBasis: 'Legal obligation to retain audit records',
  },
  {
    model: 'session',
    field: 'lastActivityAt',
    retentionDays: 90,
    action: 'delete',
    legalBasis: 'Inactive sessions are not necessary for the service',
  },
  {
    model: 'deletedUser',
    field: 'deletedAt',
    retentionDays: 30, // 30-day grace period post-deletion
    action: 'delete',
    legalBasis: 'Grace period completed, definitive deletion',
  },
  {
    model: 'analyticsEvent',
    field: 'createdAt',
    retentionDays: 180,
    action: 'anonymize',
    legalBasis: 'Anonymized analytics data for aggregate statistics',
  },
];

export async function executeRetentionPolicies() {
  logger.info({ event: 'retention_job_started' }, 'Starting data retention job');

  for (const policy of RETENTION_POLICIES) {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - policy.retentionDays);

    try {
      // Example for the session model
      if (policy.model === 'session' && policy.action === 'delete') {
        const result = await prisma.session.deleteMany({
          where: {
            [policy.field]: { lt: cutoffDate },
          },
        });

        logger.info({
          event: 'retention_policy_executed',
          model: policy.model,
          action: policy.action,
          recordsAffected: result.count,
          cutoffDate: cutoffDate.toISOString(),
          legalBasis: policy.legalBasis,
        }, `Retention policy executed: ${policy.model}`);
      }

      // Add more models as needed
    } catch (error) {
      logger.error({
        event: 'retention_policy_error',
        model: policy.model,
        error,
      }, `Error executing retention policy: ${policy.model}`);
    }
  }

  logger.info({ event: 'retention_job_completed' }, 'Data retention job completed');
}
```

---

### 5. Cookie Banner and Cookie Consent

```typescript
// middleware/cookie-consent.middleware.ts

import { Request, Response, NextFunction } from 'express';

/**
 * Cookie categories per GDPR and ePrivacy Directive.
 * Only essential cookies can be used without consent.
 */
enum CookieCategory {
  ESSENTIAL = 'essential',         // No consent required
  FUNCTIONAL = 'functional',       // Requires consent
  ANALYTICS = 'analytics',         // Requires consent
  ADVERTISING = 'advertising',     // Requires consent
}

interface CookiePreferences {
  essential: true;      // Always true, cannot be disabled
  functional: boolean;
  analytics: boolean;
  advertising: boolean;
}

/**
 * Middleware that checks the user's cookie preferences
 * before setting non-essential cookies.
 */
export function cookieConsentGuard(category: CookieCategory) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (category === CookieCategory.ESSENTIAL) {
      return next(); // Essential cookies are always allowed
    }

    const consentCookie = req.cookies?.cookie_consent;
    
    if (!consentCookie) {
      // No consent: do not set non-essential cookies
      return next();
    }

    try {
      const preferences: CookiePreferences = JSON.parse(consentCookie);
      
      if (!preferences[category]) {
        // User did not consent to this category
        // Do not set the cookie and continue
        res.locals.cookieBlocked = category;
        return next();
      }

      next();
    } catch {
      next(); // On error, do not set cookies
    }
  };
}

/**
 * Helper to set cookies respecting consent.
 */
export function setConsentAwareCookie(
  res: Response,
  name: string,
  value: string,
  category: CookieCategory,
  options: { maxAge?: number; httpOnly?: boolean; secure?: boolean; sameSite?: 'strict' | 'lax' | 'none' } = {}
) {
  if (res.locals.cookieBlocked === category) {
    return; // Cookie blocked due to lack of consent
  }

  res.cookie(name, value, {
    httpOnly: options.httpOnly ?? true,
    secure: options.secure ?? process.env.NODE_ENV === 'production',
    sameSite: options.sameSite ?? 'lax',
    maxAge: options.maxAge,
    path: '/',
  });
}
```

---

## Agent workflow

1. Identify what personal data the application processes and document the legal basis (Art. 6) for each processing activity.
2. Implement the granular consent system with Prisma schema, service, and verification middleware.
3. Implement the data subject rights service (access, rectification, erasure, restriction, portability, objection).
4. Add the privacy REST API with endpoints for data access, deletion, portability, and consent management.
5. Implement encryption of personal data at rest (AES-256-GCM) and pseudonymization where applicable.
6. Configure the data retention policy as a cron job with per-model actions (anonymize/delete/archive).
7. Implement cookie banner with granular consent per category (essential/functional/analytics/advertising).
8. Validate against the compliance checklist before deploying.

## Gotchas

Implement Privacy by Design and Privacy by Default (Art. 25): every new feature must be evaluated against GDPR before implementation, and the most restrictive privacy settings are the defaults. Do not use pre-checked consent — it must be a clear affirmative action (Art. 7). Withdrawing consent must be as easy as granting it. Maintain a Record of Processing Activities (ROPA, Art. 30). Conduct DPIAs (Art. 35) for large-scale processing, profiling, or new technologies. Security breaches must be reported to the supervisory authority within 72 hours (Art. 33) and to affected individuals without delay if there is high risk (Art. 34). Do not transfer data outside the EU without a legal mechanism (SCCs, adequacy, BCRs). Do not store data without a retention policy. Do not log personal data in plain text. Do not use personal data in development/testing environments without anonymizing.

---

## GDPR Compliance Checklist

- [ ] Legal basis documented for each data processing activity
- [ ] Granular consent system implemented
- [ ] Clear and accessible privacy policy
- [ ] Data subject rights API (access, deletion, portability, rectification)
- [ ] Encryption of personal data at rest and in transit
- [ ] Data retention policy defined and implemented
- [ ] Cookie banner with granular consent
- [ ] Record of Processing Activities (ROPA)
- [ ] Breach notification process (72 hours)
- [ ] DPIA for high-risk processing
- [ ] Legal mechanism for international transfers
- [ ] Contracts with data processors
- [ ] DPO designated (if applicable per Art. 37)
- [ ] Audit logs for personal data access
- [ ] Pseudonymization/anonymization where possible
- [ ] Development team training on GDPR

---

## References and Resources

- [Official GDPR Text](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
- [AEPD Guidelines (Spain)](https://www.aepd.es/)
- [ICO Guidelines (UK)](https://ico.org.uk/for-organisations/guide-to-data-protection/)
- [EDPB Guidelines](https://edpb.europa.eu/our-work-tools/general-guidance_en)
- [GDPR Enforcement Tracker](https://www.enforcementtracker.com/)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.