Identity Data Retention
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 viaDELETE /v1/me(R5).- Audit log do admin service (
audit_logda RFC-007) — tem policypró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_agentcru →NULL; mantémdevice_os/device_browserparseados (após #076).geo_lat/geo_lon→ null; mantémcountry_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*
usersrow → deletada.user_identities→ deletadas.auth_flowscomuser_id=<self>→ deletados imediatamente.sso_sessionscomuser_id=<self>→ revoked + deletados após 24h(grace pra reverter accidental delete via support).
lockoutscomuser_id=<self>→ deletados.auth_eventscomuser_id=<self>→ *nonimizados* nãodeletados (legitimate interest: fraud forensics pós-fato).
user_id→hash(user_id + salt);ip_address→ hashed; rawuser_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_eventscom 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 deusers+user_identities.sessions— todassso_sessions(ativas e expiradas noperíodo de R3).
events— todosauth_eventsno período de R2 (nãoanonimizados — 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.
- Rate
limited: 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(default6h).KODER_ID_RETENTION_FLOWS(default24h).KODER_ID_RETENTION_EVENTS_SUCCESS(default24m— i.e. 24 meses).KODER_ID_RETENTION_EVENTS_FAILURE(default12m).KODER_ID_RETENTION_SESSIONS_POST_EXPIRY(default90d).KODER_ID_RETENTION_LOCKOUTS_POST_UNLOCK(default30d).KODER_ID_ERASURE_GRACE(default24h).KODER_ID_PURGE_INTERVAL=0desativa pruning (dev / debugging).
Futuro — TTL declarativo (kdb-ts)
Quando o store migrar pra kdbts (R4 viram *TL declarativo no schema* eliminando a goroutine:infra/data/kdb), R1
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/multi
tenantbydefault.kmd`*garante que crosstenantreads retornam 404 — purge enxerga só dados que pertencem ao tenant correto.
- *specsmulti-tenancycontract.kmd
T9** validaDELETE` cascadeem 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=MNão-objetivos
- *uditoria full
text searchable*dos events — vem com #074 (admin audit_log) + kdbsearch. - *eal
time 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.