# Account deletion confirmed with cooling-off period

- **Pattern:** `ab-000722` (`community-privacy-controls.data-rights.deletion-cooling-off`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000722
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000722)

## Why it matters

Instant, irreversible account deletion with a single confirmation click is a UX failure that creates real harm — users can accidentally delete years of content, and phishing attacks can trick users into triggering deletion. GDPR Art. 17 does not require immediate deletion; a grace period is both compliant and protective. Without a cooling-off window and email confirmation, there is no mechanism to detect unauthorized deletion requests (e.g., from a stolen session token) before the damage is permanent.

## Severity rationale

High because irreversible instant deletion with no grace period eliminates the only practical safeguard against accidental or malicious account removal.

## Remediation

Schedule deletion rather than executing it immediately. Create a `deletion_requests` table, send a confirmation email, and run the actual erasure only after the grace period expires. In `src/app/api/account/delete/route.ts`:

```ts
async function initiateAccountDeletion(userId: string, userEmail: string) {
  const scheduledFor = new Date();
  scheduledFor.setDate(scheduledFor.getDate() + 30); // 30-day grace

  const request = await db.deletionRequest.create({
    data: { userId, status: 'pending', scheduledFor, confirmationToken: generateToken() }
  });

  await sendEmail({
    to: userEmail,
    subject: 'Confirm account deletion',
    body: `Your account is scheduled for deletion on ${scheduledFor.toDateString()}. Cancel here: ${baseUrl}/account/deletion-cancel?token=${request.confirmationToken}`
  });

  return request;
}
```

Run a daily cron job that processes `deletionRequests` where `scheduledFor < NOW()` and `status = 'confirmed'`, then invokes the full erasure logic.

## Detection

- **ID:** `deletion-cooling-off`
- **Severity:** `high`
- **What to look for:** Enumerate every relevant item. Examine account deletion flow. Check whether deletion is immediate or requires a confirmation/cooling-off period. Look for UI that asks for confirmation, sends a confirmation email, or schedules deletion for later. Verify the period is documented (e.g., 30-day grace period).
- **Pass criteria:** At least 1 of the following conditions is met. Account deletion requires explicit confirmation (two-factor) and has a documented grace period (e.g., 7-30 days) during which the account can be restored. After the period, deletion becomes irreversible. User receives email confirmation.
- **Fail criteria:** Deletion is immediate with no cooling-off period. No confirmation mechanism. User cannot cancel deletion after requesting it.
- **Skip (N/A) when:** Never — deletion confirmation is a user protection.
- **Detail on fail:** Describe the deletion flow. Example: `"Account deletion is immediate with one-click confirmation. No grace period, no email confirmation, no way to restore."` or `"Confirmation email sent but deletion executes instantly without waiting for user to confirm."`
- **Remediation:** Implement deletion with cooling-off period:

  ```ts
  async function initiateAccountDeletion(userId: string) {
    const deletionScheduled = new Date();
    deletionScheduled.setDate(deletionScheduled.getDate() + 30); // 30-day grace period

    const request = await db.deletionRequest.create({
      data: {
        userId,
        status: 'pending',
        scheduledFor: deletionScheduled,
        confirmationToken: generateToken(),
      }
    });

    // Send confirmation email
    await sendEmail({
      to: user.email,
      subject: 'Confirm account deletion',
      body: `Click here to confirm: ${baseUrl}/delete-confirm?token=${request.confirmationToken}`
    });

    return request;
  }

  async function confirmAccountDeletion(token: string) {
    const request = await db.deletionRequest.findUnique({
      where: { confirmationToken: token }
    });

    if (new Date() < request.scheduledFor) {
      return { message: `Deletion scheduled for ${request.scheduledFor}. You can cancel until then.` };
    }

    // After grace period, execute deletion
    await deleteUserPermanently(request.userId);
  }
  ```

## External references

- gdpr Art. 17
- ccpa §1798.105
- iso-27001 A.8.10

Taxons: privacy-consent, user-experience

HTML version: https://auditbuffet.com/patterns/ab-000722
