Usuarios y acceso
La oficina no tiene registro abierto: entra quien tiene una fila activa en la tabla
organtia.acceso_usuarios. Todo lo demás —el formulario, la cookie, el cierre de sesión—
existe alrededor de esa tabla.
Quién puede entrar
Sección titulada «Quién puede entrar»Cada instalación tiene su propia lista de usuarios. No hay una puerta común entre clientes y
no hay invitados: si no estás en la tabla, o tu fila tiene activo = false, la puerta
responde «Usuario o contraseña incorrectos» y no distingue tu caso de una contraseña mal
escrita.
| Situación | Qué ocurre |
|---|---|
Usuario en la tabla con activo = true y contraseña correcta |
Entra y se actualiza ultimo_acceso. |
| Usuario que no existe | Se calcula igualmente un hash (para no delatar qué usuarios hay) y se cuenta el intento. |
Usuario con activo = false |
Se trata como si no existiera. |
Sin ACCESO_CLAVE en el entorno (o con menos de 32 caracteres) |
No entra nadie: la puerta falla cerrada, no abierta. |
Tipos de cuenta: hay uno solo
Sección titulada «Tipos de cuenta: hay uno solo»Conviene decirlo antes de buscar lo que no hay. No existen roles ni permisos por usuario. La tabla no tiene columna de rol, la aplicación no comprueba permisos distintos y todas las personas con cuenta ven y hacen lo mismo: la oficina entera, con sus fichas, sus aprobaciones y su configuración.
Lo que sí distingue a unas personas de otras es el rastro: la auditoría guarda el nombre del
propietario o el usuario de la puerta en el campo actor de cada operación (ver
/docs/administracion/auditoria/). Es trazabilidad, no
control de acceso.
El CEO y los directores no son usuarios de esta tabla: son puestos, viven en
organtia.puestos y no entran por la puerta.
La sesión
Sección titulada «La sesión»Al entrar se crea una cookie llamada oficina_sesion con la forma
usuario|expira|firma. La firma es un HMAC-SHA256 con la clave ACCESO_CLAVE; la caducidad
la comprueba el servidor por su cuenta, porque el navegador puede mentir sobre la fecha.
La cookie se emite con HttpOnly, Secure y SameSite=Lax. Dura lo que diga ACCESO_DIAS:
| Variable | Por defecto | Qué hace |
|---|---|---|
ACCESO_CLAVE |
sin valor | Clave con la que se firman las cookies. Debe tener 32 caracteres o más. Si cambia, todas las sesiones abiertas dejan de valer y hay que volver a entrar. |
ACCESO_DIAS |
7 |
Días de validez de la cookie. Se admite de 1 a 90; un valor fuera de rango o no numérico se queda en 7. |
Caddy no sabe de contraseñas: antes de servir la oficina pregunta a /acceso/authz si hay
sesión. Si la hay, recibe un 200 y la cabecera Remote-User con el usuario, que es lo que la
aplicación muestra en la barra superior y lo que usa para atribuir las peticiones.
Crear un usuario
Sección titulada «Crear un usuario»No se crea desde la web. Se crea con un script que va dentro del contenedor:
docker compose exec -T api python scripts/crear_usuario_oficina.py pedroPide la contraseña dos veces por teclado, sin argumentos: lo que se escribe en la línea de
comandos queda en el historial del shell y en ps. El usuario se guarda en minúsculas y el
nombre visible se rellena con la primera letra en mayúscula.
Formas de uso:
| Comando | Qué hace |
|---|---|
... crear_usuario_oficina.py pedro |
Crea el usuario o le cambia la contraseña si ya existe. |
... crear_usuario_oficina.py --listar |
Lista usuario, si está activo, último acceso y nombre. |
... crear_usuario_oficina.py --desactivar pedro |
Pone activo = false. No borra la fila ni sus datos. |
... crear_usuario_oficina.py pedro --por-tuberia |
Lee la contraseña de la entrada estándar, dos líneas. Solo para automatizar. |
Requisitos de la contraseña: 8 caracteres como mínimo. No hay reglas de mayúsculas, símbolos ni caducidad: el mínimo es bajo a propósito, para no estorbar al propietario.
Cambiar la contraseña
Sección titulada «Cambiar la contraseña»El propio usuario la cambia en /acceso/clave, con la sesión abierta. Pide la actual, la
nueva y la repetición, y comprueba que la nueva no sea igual que la actual. Al guardarla se
renueva la cookie de ese navegador.
La contraseña no se guarda en ningún sitio: se guarda scrypt(clave, sal) con coste
N=2^14, r=8, p=1 y una sal distinta por usuario. La comparación es en tiempo constante, y un
usuario inexistente paga el mismo cálculo que uno real.
Cerrar sesión
Sección titulada «Cerrar sesión»El botón de salir de la barra superior manda a /acceso/salir, que borra la cookie y vuelve
al formulario.
Además, la interfaz vigila la inactividad: a los 30 minutos sin tocar nada (teclado, ratón, rueda o pantalla táctil) muestra un aviso con «Tu sesión se cerrará en un minuto si no haces nada» y un botón «Seguir aquí». Ese aviso no cierra la sesión por sí solo: la cookie sigue siendo la que manda. Lo que hace es, al volver a la pestaña, preguntar a la puerta si aún acepta la sesión; si ya no, aparece «La sesión se ha cerrado por seguridad. No se ha perdido nada» con un botón para entrar de nuevo.
Intentos fallidos
Sección titulada «Intentos fallidos»Cinco intentos por usuario cada cinco minutos. Al sexto, la puerta responde igual que ante una contraseña incorrecta y escribe un aviso en el registro del proceso:
Acceso a la oficina bloqueado temporalmente: pedroEl contador vive en memoria: si se reinicia el servicio, se olvida. Es deliberado —lo que no valía era no tener nada— pero conviene saberlo antes de contar con él como defensa duradera.
Desactivar a alguien
Sección titulada «Desactivar a alguien»docker compose exec -T api python scripts/crear_usuario_oficina.py --desactivar pedroDeja de poder entrar de inmediato. Lo que no hace es cortar la sesión que ya tenga
abierta: la cookie se valida por firma y fecha, sin consultar la base de datos en cada
petición, así que un navegador ya autenticado sigue dentro hasta que caduque (hasta
ACCESO_DIAS días). Si necesitas cortar ya, cambia ACCESO_CLAVE y reinicia la API: eso
invalida todas las cookies de todas las personas.
Por el mismo motivo, cambiar la contraseña a alguien no cierra sus otras sesiones.
La tabla
Sección titulada «La tabla»organtia.acceso_usuarios (migración 028_usuarios_oficina.sql):
| Campo | Tipo | Qué significa |
|---|---|---|
id |
bigserial | Identificador interno. |
usuario |
text, único | El nombre con el que se entra, en minúsculas. No es el correo. |
nombre |
text | Nombre visible. El script lo rellena con el usuario capitalizado; se conserva si se actualiza la contraseña sin dar nombre. |
hash |
text | Resultado de scrypt en hexadecimal. Nunca la contraseña. |
sal |
text | Sal de 16 bytes en hexadecimal, distinta por usuario. |
activo |
boolean | Si es false, la puerta lo ignora. Volver a guardar la contraseña lo pone en true. |
creado_en |
timestamptz | Alta. |
ultimo_acceso |
timestamptz | Última entrada correcta. Lo escribe cada autenticación; null significa que nunca ha entrado. |
La tabla no tiene columna de doble factor todavía. El código la deja preparada para añadirlo encima sin tocar lo demás.
Lo que no hay
Sección titulada «Lo que no hay»Para que no lo busques:
- No hay alta ni baja desde la interfaz: solo el script.
- No hay recuperación de contraseña por correo ni pregunta de seguridad. Si alguien la pierde, se la cambias con el script.
- No hay doble factor propio.
- No hay roles, grupos ni permisos por persona.
- No hay caducidad de contraseña ni historial de contraseñas anteriores.
- Los intentos de entrada no van a la tabla de auditoría: van al registro del proceso.
Problemas típicos
Sección titulada «Problemas típicos»| Lo que ves | Causa probable | Qué hacer |
|---|---|---|
| «Usuario o contraseña incorrectos» con la contraseña correcta | Usuario desactivado, o espacio al final al pegarla | Comprueba --listar y vuelve a escribirla a mano. |
| Entras y vuelves al formulario en bucle | El navegador no guarda la cookie, o entras por http:// en vez de https:// (la cookie es Secure) |
Usa la dirección pública con HTTPS y acepta cookies del sitio. |
| A los cinco intentos fallidos no entra ni con la buena | Bloqueo temporal de cinco minutos | Espera cinco minutos o reinicia la API. |
| La puerta da error 500 y no muestra formulario | Falta ACCESO_CLAVE o tiene menos de 32 caracteres |
Define la variable en .env y recrea el servicio api. |
| Cambiaste la contraseña y sigue entrando desde otro sitio | La cookie de ese navegador no ha caducado | Cambia ACCESO_CLAVE si necesitas cortar todas las sesiones. |
