diff --git a/app.md b/app.md index 150219fc..561c2614 100644 --- a/app.md +++ b/app.md @@ -2,7 +2,7 @@ name: unibus lang: go domain: infra -version: 0.9.0 +version: 0.10.0 description: "Bus de mensajería unificado sobre NATS+JetStream con cifrado E2E por room (megolm/olm reducido): service de membresía/claves, librería cliente y peers demo." tags: [service, messaging, nats, e2e] uses_functions: @@ -122,6 +122,21 @@ Para apuntar a un NATS externo en producción: `--nats-url nats://host:4222` en las rutas GET de lectura. Confía en la red interna. Las rutas mutantes (`/rooms`, `/invite`, `/rekey`) sí exigen firma Ed25519 del owner sobre los bytes canónicos de la request. Endurecer es fase posterior. +- **Gestión de usuarios: storage unificado, alta por dos vías.** El allowlist de + usuarios vive en el MISMO store que las rooms (`pkg/membership.Store`): SQLite en + single-node, JetStream KV replicado (`UNIBUS_users`) en cluster. El `Server` ya + tiene ese store privilegiado abierto (es quien sirve el KV en cada nodo), así que + expone `GET/POST /users` y `POST /users/{signpub}/revoke` como API HTTP admin-only, + simétrica con las rutas de rooms: el panel de administración firma como admin y el + server ejecuta la mutación contra el mismo store. El panel NO necesita `--db`, ni la + identidad interna, ni correr en un nodo del cluster; funciona idéntico en single-node + y cluster. La autorización es default-deny: solo un firmante que el store confirma como + `role == "admin"` activo pasa, cualquier otro recibe 403 (encima de la firma+nonce+TLS + ya existentes). La CLI `membershipd user add --store kv` sigue existiendo SOLO para + sembrar el admin #0 (bootstrap del huevo-gallina: sin un admin sembrado no hay quién + firme el primer `POST /users`); a partir de ahí toda la gestión es HTTP admin-only. El + alta es idempotente igual que la CLI: re-alta de una clave ya registrada = 409, sin + sobrescribir ni elevar rol; el revoke es un flip de status (sin hard-delete), auditable. - **Identidad = secreto crítico.** El archivo de identidad (`worker.id`, `chat.id`) contiene las claves privadas (Ed25519 + X25519). Se escribe 0600. Perderlo = mensajes ilegibles, sin recuperación. Trátalo como una clave SSH. @@ -154,6 +169,28 @@ agent..{in,out} inbox/outbox de agente LLM (agent.scout.in) ## Capability growth log +- v0.10.0 (2026-06-07) — API HTTP admin-only de gestión de usuarios, cerrando la + última asimetría del control plane: las rooms tenían superficie HTTP firmada + (`POST /rooms`, etc.) pero los users solo se gestionaban por CLI local o acceso + directo al store. Se añaden `GET /users` (lista completa, incluidos revocados), + `POST /users` (alta `{sign_pub, handle, role}`: valida hex de 64 chars + role en + `{admin, member}`, 409 idempotente que no sobrescribe ni eleva rol) y + `POST /users/{signpub}/revoke` (flip de status, sin hard-delete). Los tres pasan por + un helper `requireAdmin` default-deny que confirma contra el store que el firmante + autenticado es un user `role == "admin"` activo (el endpoint id es un hash one-way de + la clave, así que el contexto lleva ahora también el `sign_pub` hex del firmante para + resolver `GetUser`); cualquier otro firmante recibe 403, encima de la firma+nonce+TLS+ + enforce ya heredadas del middleware. NO se abre conexión KV nueva ni se usa la identidad + interna: el server escribe vía su `s.store` privilegiado, el MISMO que las rooms (SQLite + single-node, KV `UNIBUS_users` en cluster). `pkg/client` gana `ListUsers/AddUser/RevokeUser` + (tipo plano `UserInfo`) firmando como admin, así la pestaña Users del panel deja de + necesitar `--db`/acceso KV directo. La CLI `membershipd user add --store kv` queda SOLO + para sembrar el admin #0 (bootstrap). La validación de `sign_pub` se unifica en + `membership.ValidateSignPubHex`, reusada por la CLI y los handlers. Tests nuevos: + no-admin → 403 en los tres endpoints, roundtrip admin add→list→revoke, y validación + (hex inválido → 400, role inválido → 400, re-alta → 409), más un test de cliente contra + un membershipd embebido. Cambios 100% aditivos: el comportamiento single-node y de las + rutas de rooms no cambia; vet/build/test verdes. - v0.9.0 (2026-06-07) — cierre de los gaps que el despliegue del cluster (report 0011) dejó abiertos (report 0012). (GAP A) Nueva capability `membershipd user add|list|revoke --store kv`: alta/baja de usuarios contra el KV replicado del