Identity Data Retention

mandatory

Janelas de retenção pra dados de autenticação do Koder ID (auth_flows, auth_events, sso_sessions, lockouts) + right-to-erasure + data-export. Sibling de error-reporting-retention.kmd; cobre LGPD Art. 15-16 e GDPR Art. 5(1)(e).

Identity Data Retention

Escopo

O *oder ID*(services/foundation/id) é o serviço autoritativo de autenticação da Stack. Diferente do reporter público (landlord pattern, zero-PII — coberto por error-reporting-retention.kmd), o ID *onhece o usuário* armazena identidades, sessões, tentativas de login, IPs, User-Agents e — a partir dos tickets #075#076#077 — geolocalização, parse de device e ASN.

Esta policy define *uanto tempo*cada categoria de dado vive, *omo*o usuário pode pedir export ou erasure, e *uando*o serviço prune automaticamente.

Aplica a:

  • auth_flows — flows OAuth/login transitórios (transitório, TTL curto).
  • auth_events — audit trail de tentativas de login (sucesso/falha).
  • sso_sessions — sessões ativas e expiradas.
  • lockouts — counters de brute-force.
  • Tabelas derivadas que vierem a ser adicionadas via #075#076#077

    (geo_*, device_*, asn_* columns são parte das mesmas tabelas).

*ão cobre:*

  • users, user_identities, tenants, workspaces,

    oauth_clients — tabelas de identidade core; deletionbyuser é cascateada via DELETE /v1/me (R5).

  • Audit log do admin service (audit_log da RFC-007) — tem policy

    própria a ser escrita quando #074 shipar.

  • Reporter público — coberto por error-reporting-retention.kmd.

Regras

R1 — auth_flows: TTL 24h após expires_at

Flows OAuth/login são *ransitórios* Vivem até expires_at (default 10 min). Após expirar, ficam *4h*no DB pra debugging (operador grep flow_id), depois são deletados pelo purge job.

Total lifetime: expires_at − created_at + 24h ≈ 10 min + 24h.

Justificativa: flow é estado intermediário, nunca audit. 24h é suficiente pra qualquer triage operacional; além disso é ruído.

R2 — auth_events: 24 meses (sucesso) / 12 meses (falha)

Audit trail. Retenção depende do event_type:

event_type família Retenção Justificativa
Sucesso (login_ok, mfa_ok, password_changed, etc.) *4 meses* LGPD audit window; investigação de fraude pós-fato
Falha (login_failed, mfa_failed, lockout, etc.) *2 meses* Suficiente pra spot brute-force histórico; reduz volume

*nonimização opcional a partir de 6 meses*(futuro — não MVP):

  • ip_address → hash (SHA-256(ip + salt)); salt rotaciona anualmente.
  • user_agent cru → NULL; mantém device_os/device_browser parseados (após #076).
  • geo_lat/geo_lon → null; mantém country_code.

A anonimização preserva contagens agregadas e detecção de padrões sem reter PII linkável. *ão shipa no MVP* requer #076/#075 pra ter os campos parseados; até lá, R2 é raw-only com purge bruto após 12/24 meses.

R3 — sso_sessions: até expires_at + 90 dias (audit history)

Sessão ativa: vive até expires_at natural (default *80 dias*com rotation do refresh_token a cada uso; configurável por client per specs/auth/oauth-flow.kmd §R8). Idle timeout default *0 dias*

Sessão expirada ou revoked=true: mantida por *0 dias*pra populating GET /v1/me/activity (usuário ver "sua sessão de São Paulo em 20260315 foi encerrada às 14:23"). Após 90 dias, purge.

R4 — lockouts: 30 dias após locked_until

Counter de brute-force. Mantido enquanto locked_until ≥ now (usuário ainda bloqueado). Após desbloqueio, *0 dias*de retenção pra detectar padrão repetido (atacante volta, vê o mesmo failed_attempts cumulativo). Depois, purge.

R5 — Right to erasure: DELETE /v1/me

Conforme LGPD Art. 18 §IV e GDPR Art. 17, o usuário pode pedir apagamento dos próprios dados.

*ndpoint* DELETE /v1/me (auth required; confirma com ?confirm=DELETE_MY_ACCOUNT).

*ascateamento*

  • users row → deletada.
  • user_identities → deletadas.
  • auth_flows com user_id=<self> → deletados imediatamente.
  • sso_sessions com user_id=<self> → revoked + deletados após 24h

    (grace pra reverter accidental delete via support).

  • lockouts com user_id=<self> → deletados.
  • auth_events com user_id=<self> → *nonimizados* não

    deletados (legitimate interest: fraud forensics pós-fato). user_idhash(user_id + salt); ip_address → hashed; raw user_agent → null. Mantidos por 6 meses pós-erasure; depois deletados de vez.

*omportamento esperado*

  • POST request inicia delete; resposta 202 com pending_until

    (now + 24h grace).

  • Durante o grace period, sessões ficam revoked (logout forçado),

    mas o user row ainda existe — operador pode reverter via admin API.

  • Após o grace, purge job aplica cascade + anonimização.

R6 — Backup restore respeita R1–R5

Restore de backup *ÃO*revive dados purgados:

  • Backup tem snapshot do auth_events com IPs raw de N meses atrás.
  • Após restore, o purge job (R2) imediatamente aplica anonimização

    + delete de tudo fora da janela.

Operadores que precisam reter dados pra incident investigation devem *opiar*o snapshot pra fora do DB ativo, não restore in place.

R7 — Data export: GET /v1/me/data-export

Conforme LGPD Art. 18 §V e GDPR Art. 20 (portability), o usuário pode pedir export completo dos próprios dados.

*ndpoint* GET /v1/me/data-export (auth required).

*onteúdo retornado*(JSON, paginado se > 10 MB):

  • profile — toda a row de users + user_identities.
  • sessions — todas sso_sessions (ativas e expiradas no

    período de R3).

  • events — todos auth_events no período de R2 (não

    anonimizados — usuário tem direito ao próprio dado raw).

  • lockouts — counters atuais.

*omportamento*

  • Síncrono se < 1 MB; retorna JSON inline.
  • Async (gera arquivo + envia link por email) se >= 1 MB.
  • Ratelimited: 1 export por usuário por dia (antiabuse).

Não inclui: tokens (token_hash já é hash one-way), secrets, dados de outros usuários, dados de admin.

Implementação atual e roadmap

MVP — internal/retention/ goroutine

Tickets de implementação:

  • *078 Tarefa 2*— purge job: pacote

    services/foundation/id/engine/internal/retention/ com goroutine no auth service rodando a cada 6h. Métricas Prometheus: retention_rows_purged_total{table,reason}.

  • *078 Tarefa 3*— endpoint GET /v1/me/data-export.
  • *078 Tarefa 4*— endpoint DELETE /v1/me + grace + cascade.

*onfigurável*via flags / env:

  • KODER_ID_PURGE_INTERVAL (default 6h).
  • KODER_ID_RETENTION_FLOWS (default 24h).
  • KODER_ID_RETENTION_EVENTS_SUCCESS (default 24m — i.e. 24 meses).
  • KODER_ID_RETENTION_EVENTS_FAILURE (default 12m).
  • KODER_ID_RETENTION_SESSIONS_POST_EXPIRY (default 90d).
  • KODER_ID_RETENTION_LOCKOUTS_POST_UNLOCK (default 30d).
  • KODER_ID_ERASURE_GRACE (default 24h).
  • KODER_ID_PURGE_INTERVAL=0 desativa pruning (dev / debugging).

Futuro — TTL declarativo (kdb-ts)

Quando o store migrar pra kdbts (infra/data/kdb), R1R4 viram *TL declarativo no schema* eliminando a goroutine:

CREATE TABLE auth_flows (
  ...
) WITH (ttl = '24h after expires_at');

CREATE TABLE auth_events (
  ...
) WITH (ttl = 'CASE WHEN event_type LIKE ''%_failed'' THEN 12 months ELSE 24 months END');

A goroutine fica como fallback dev-only quando o sqlite store estiver ativo.

Defesa em profundidade

Esta policy é o *aminho normal* Em paralelo:

  • *policies/multitenantbydefault.kmd`*garante que crosstenant

    reads retornam 404 — purge enxerga só dados que pertencem ao tenant correto.

  • *specsmulti-tenancycontract.kmd T9** valida DELETE` cascade

    em tests de contrato.

  • Backup encriptado at-rest com chave separada por ambiente

    (ver policies/environments.kmd).

Verificação

Testes (a virem como Tarefa 5 do #078):

  • internal/retention/purge_test.go::TestPurge_AuthFlows_* — R1.
  • internal/retention/purge_test.go::TestPurge_AuthEvents_* — R2 (sucesso vs falha).
  • internal/retention/purge_test.go::TestPurge_SsoSessions_* — R3.
  • internal/retention/purge_test.go::TestPurge_Lockouts_* — R4.
  • internal/retention/erasure_test.go::TestErasure_Cascade — R5.
  • internal/retention/export_test.go::TestExport_Shape — R7.

Operacional:

# Confirma que o purge tá rodando
ssh s.k.lin 'journalctl -u koder-id-auth.service | grep "retention.purge"'

# Output esperado a cada 6h:
#   retention.purge table=auth_flows purged=N kept=M
#   retention.purge table=auth_events purged=N kept=M reason=success_24m
#   retention.purge table=auth_events purged=N kept=M reason=failure_12m
#   retention.purge table=sso_sessions purged=N kept=M
#   retention.purge table=lockouts purged=N kept=M

Não-objetivos

  • *uditoria fulltext searchable*dos events — vem com #074 (admin audit_log) + kdbsearch.
  • *ealtime alerting*sobre delete patterns — fora do escopo (vai pro reporter ou kdbstream).
  • *ackup retention*independente — backups têm policy própria

    no infra/data/backup (não escrita ainda).

  • *ompliance certification*(SOC2 / ISO27001) — esta policy

    fornece base; certificação requer auditoria externa.

Source: ../home/koder/dev/koder/meta/docs/stack/policies/identity-data-retention.kmd