Skip to content

Exportação de Dados Pessoais ​

Uma exportação de dados no estilo GDPR permite que os usuários baixem todos os seus dados pessoais. As principais preocupações são: exclusão de campos sensíveis do payload de exportação, tokens de download seguros e aplicação de expiração.

Componentes principais ​

  • Job de exportação: um registro que vincula um usuário a um token de download opaco, com status (pending → ready) e um timestamp de expiração.
  • Etapa de processamento: uma operação do lado do worker que constrói o payload e marca o job como pronto.
  • Download: busca o payload pelo token, verificando a expiração antes de servir.

Schema ​

sql
CREATE TABLE data_exports (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id    INTEGER NOT NULL,
    token      TEXT    NOT NULL UNIQUE,
    status     TEXT    NOT NULL DEFAULT 'pending',
    payload    TEXT,
    expires_at TEXT    NOT NULL,
    created_at TEXT    NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Geração do token ​

Use bin2hex(random_bytes(32)) — 64 caracteres hex, 256 bits de entropia. IDs sequenciais, timestamps ou tokens baseados em MD5 são adivináveis e não devem ser usados para tokens de download.

php
$token = bin2hex(random_bytes(32));

Exclusão de campos sensíveis ​

O payload de exportação nunca deve conter credenciais ou campos para os quais o usuário não consentiu explicitamente a exportação. Exclua no nível do repositório, não na camada HTTP:

php
public function processExport(string $token, User $user, array $activities, string $now): DataExport
{
    $payload = json_encode([
        'exported_at' => $now,
        'user' => [
            'id'         => $user->id,
            'email'      => $user->email,
            'name'       => $user->name,
            'created_at' => $user->createdAt,
            // password_hash intencionalmente excluído
            // phone intencionalmente excluído (requer reconsentimento para PII)
        ],
        'activities' => $activities,
    ], JSON_THROW_ON_ERROR);
    // ...
}

Aplique a mesma exclusão ao endpoint de perfil público — phone, password_hash e quaisquer campos internos não devem aparecer nas respostas de GET /users/{id} também.

Aplicação da expiração ​

Aplique a expiração em ambos o endpoint de download e o endpoint de processamento:

php
// Em downloadExport:
if ($export->isExpired($now)) {
    return $this->problems->create($request, 'gone', 'Export has expired.', 410, '');
}

// Em processExport — CRÍTICO: também verificar aqui
if ($export->isExpired($now)) {
    return $this->problems->create($request, 'gone', 'Export request has expired. Please request a new export.', 410, '');
}

Sem a verificação em processExport, um worker que recebe um job desatualizado escreveria os dados do usuário no banco mesmo após o fechamento da janela de download, criando registros órfãos com dados de payload sensíveis.

Fluxo de status ​

pending ──(processamento chamado, não expirado)──▶ ready ──(download chamado)──▶ [payload servido]
   │                                                          │
   └──(processamento chamado, expirado)──▶ 410               └──(expirado)──▶ 410

Download: 410 Gone vs 404 Not Found ​

  • 404: o token não existe no banco de dados.
  • 410 Gone: o token existe, mas expirou. Este é o status correto — o recurso existia e foi removido desde então. Os clientes podem usar esse sinal para solicitar ao usuário que faça uma nova exportação.

Decisões de design ​

Por que uma etapa process separada em vez de geração síncrona? Payloads de exportação podem ser grandes (anos de dados de atividade). A geração síncrona no handler HTTP corre risco de timeouts e ocupa um worker. O padrão assíncrono permite que o usuário solicite e verifique mais tarde. Para este FT, a etapa de processamento é exposta como uma API para simular a invocação do worker.

Por que usar o token como URL de download em vez do ID de exportação? Um ID inteiro sequencial é vulnerável a IDOR — o usuário 1 poderia baixar a exportação do usuário 2 incrementando o ID. Um token aleatório opaco torna a URL de download impossível de adivinhar.

A etapa process deve ser um endpoint público? Em produção, não. O endpoint de processamento deve ser chamado apenas por workers internos (via chave de API, rede interna ou fila). Neste FT está exposto para testabilidade. A entropia do token fornece alguma proteção, mas não é substituta para autenticação adequada de workers.

Publicado sob a licença MIT.